tomparse 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.index +2 -2
- data/.yardopts +8 -0
- data/HISTORY.md +26 -1
- data/README.md +92 -7
- data/lib/tomparse.rb +43 -482
- data/lib/tomparse.yml +68 -0
- data/lib/tomparse/argument.rb +69 -0
- data/lib/tomparse/option.rb +30 -0
- data/lib/tomparse/parse_error.rb +30 -0
- data/lib/tomparse/parser.rb +699 -0
- data/test/helper.rb +0 -20
- data/test/test_description.rb +54 -0
- data/test/test_prefixes.rb +3 -3
- data/test/test_signatures.rb +44 -25
- data/test/test_tags.rb +6 -4
- data/test/test_tomdoc.rb +3 -3
- data/test/test_yields.rb +17 -0
- metadata +12 -3
- data/.rubyrc +0 -19
data/lib/tomparse.yml
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
---
|
2
|
+
revision: 2013
|
3
|
+
type: ruby
|
4
|
+
sources:
|
5
|
+
- var
|
6
|
+
- VERSION
|
7
|
+
authors:
|
8
|
+
- name: trans
|
9
|
+
email: transfire@gmail.com
|
10
|
+
organizations:
|
11
|
+
- name: Rubyworks
|
12
|
+
website: http://rubyworks.github.com
|
13
|
+
requirements:
|
14
|
+
- groups:
|
15
|
+
- test
|
16
|
+
development: true
|
17
|
+
name: citron
|
18
|
+
- groups:
|
19
|
+
- test
|
20
|
+
development: true
|
21
|
+
name: ae
|
22
|
+
- groups:
|
23
|
+
- build
|
24
|
+
development: true
|
25
|
+
name: detroit
|
26
|
+
conflicts: []
|
27
|
+
alternatives: []
|
28
|
+
resources:
|
29
|
+
- type: home
|
30
|
+
uri: http://rubyworks.github.com/tomparse
|
31
|
+
label: Homepage
|
32
|
+
- type: code
|
33
|
+
uri: http://github.com/rubyworks/tomparse
|
34
|
+
label: Source Code
|
35
|
+
- type: api
|
36
|
+
uri: http://rubydoc.info/gems/tomparse/frames
|
37
|
+
label: API Guide
|
38
|
+
- type: mail
|
39
|
+
uri: http://groups.google.com/group/rubyworks-mailinglist
|
40
|
+
label: Mailing List
|
41
|
+
- type: chat
|
42
|
+
uri: http://chat.us.freenode.net/rubyworks
|
43
|
+
label: IRC Channel
|
44
|
+
repositories:
|
45
|
+
- name: upstream
|
46
|
+
scm: git
|
47
|
+
uri: http://github.com/rubyworks/tomparse/tomparse.git
|
48
|
+
categories:
|
49
|
+
- documentation
|
50
|
+
copyrights:
|
51
|
+
- holder: Rubyworks
|
52
|
+
year: '2012'
|
53
|
+
license: BSD-2-Clause
|
54
|
+
customs: []
|
55
|
+
paths:
|
56
|
+
lib:
|
57
|
+
- lib
|
58
|
+
created: '2012-03-04'
|
59
|
+
summary: TomDoc parser for Ruby
|
60
|
+
title: TomParse
|
61
|
+
name: tomparse
|
62
|
+
description: ! 'TomParse provides no other functionality than to take a code comment
|
63
|
+
|
64
|
+
and parse it in to a convenient object-oriented structure in accordance
|
65
|
+
|
66
|
+
with TomDoc standard.'
|
67
|
+
version: 0.4.0
|
68
|
+
date: '2013-02-11'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module TomParse
|
2
|
+
|
3
|
+
# Encapsulate a method argument.
|
4
|
+
#
|
5
|
+
class Argument
|
6
|
+
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
attr_accessor :description
|
10
|
+
|
11
|
+
attr_accessor :options
|
12
|
+
|
13
|
+
# Create new Argument object.
|
14
|
+
#
|
15
|
+
# name - name of argument
|
16
|
+
# description - argument description
|
17
|
+
#
|
18
|
+
def initialize(name, description = '')
|
19
|
+
@name = name.to_s.intern
|
20
|
+
parse(description)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is this an optional argument?
|
24
|
+
#
|
25
|
+
# Returns Boolean.
|
26
|
+
def optional?
|
27
|
+
@description.downcase.include? 'optional'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse arguments section. Arguments occur subsequent to
|
31
|
+
# the description.
|
32
|
+
#
|
33
|
+
# section - String containing argument definitions.
|
34
|
+
#
|
35
|
+
# Returns nothing.
|
36
|
+
def parse(description)
|
37
|
+
desc = []
|
38
|
+
opts = []
|
39
|
+
|
40
|
+
lines = description.lines.to_a
|
41
|
+
|
42
|
+
until lines.empty? or /^\s+\:(\w+)\s+-\s+(.*?)$/ =~ lines.first
|
43
|
+
desc << lines.shift.chomp.squeeze(" ")
|
44
|
+
end
|
45
|
+
|
46
|
+
opts = []
|
47
|
+
last_indent = nil
|
48
|
+
|
49
|
+
lines.each do |line|
|
50
|
+
next if line.strip.empty?
|
51
|
+
indent = line.scan(/^\s*/)[0].to_s.size
|
52
|
+
|
53
|
+
if last_indent && indent > last_indent
|
54
|
+
opts.last.description << line.squeeze(" ")
|
55
|
+
else
|
56
|
+
param, d = line.split(" - ")
|
57
|
+
opts << Option.new(param.strip, d.strip) if param && d
|
58
|
+
end
|
59
|
+
|
60
|
+
last_indent = indent
|
61
|
+
end
|
62
|
+
|
63
|
+
@description = desc.join
|
64
|
+
@options = opts
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module TomParse
|
2
|
+
|
3
|
+
# Encapsulate a named parameter.
|
4
|
+
#
|
5
|
+
class Option
|
6
|
+
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
attr_accessor :description
|
10
|
+
|
11
|
+
# Create new Argument object.
|
12
|
+
#
|
13
|
+
# name - name of option
|
14
|
+
# description - option description
|
15
|
+
#
|
16
|
+
def initialize(name, description = '')
|
17
|
+
@name = name.to_s.intern
|
18
|
+
@description = description
|
19
|
+
end
|
20
|
+
|
21
|
+
# Is this a required option?
|
22
|
+
#
|
23
|
+
# Returns Boolean.
|
24
|
+
def required?
|
25
|
+
@description.downcase.include? 'required'
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module TomParse
|
2
|
+
|
3
|
+
# Raised when comment can't be parsed, which means it's most
|
4
|
+
# likely not valid TomDoc.
|
5
|
+
#
|
6
|
+
class ParseError < RuntimeError
|
7
|
+
# Create new ParseError object.
|
8
|
+
#
|
9
|
+
# doc - document string
|
10
|
+
#
|
11
|
+
def initialize(doc)
|
12
|
+
@doc = doc
|
13
|
+
end
|
14
|
+
|
15
|
+
# Provide access to document string.
|
16
|
+
#
|
17
|
+
# Returns String.
|
18
|
+
def message
|
19
|
+
@doc
|
20
|
+
end
|
21
|
+
|
22
|
+
# Provide access to document string.
|
23
|
+
#
|
24
|
+
# Returns String.
|
25
|
+
def to_s
|
26
|
+
@doc
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,699 @@
|
|
1
|
+
module TomParse
|
2
|
+
|
3
|
+
# Encapsulate parsed tomdoc documentation.
|
4
|
+
#
|
5
|
+
# TODO: Currently uses lazy evaluation, eventually this should
|
6
|
+
# be removed and simply parsed all at once.
|
7
|
+
#
|
8
|
+
class Parser
|
9
|
+
|
10
|
+
#
|
11
|
+
attr_accessor :raw
|
12
|
+
|
13
|
+
# Public: Initialize a TomDoc object.
|
14
|
+
#
|
15
|
+
# text - The raw text of a method or class/module comment.
|
16
|
+
#
|
17
|
+
# Returns new TomDoc instance.
|
18
|
+
def initialize(text, parse_options={})
|
19
|
+
@raw = text.to_s.strip
|
20
|
+
|
21
|
+
@arguments = []
|
22
|
+
@options = []
|
23
|
+
@examples = []
|
24
|
+
@returns = []
|
25
|
+
@raises = []
|
26
|
+
@signatures = []
|
27
|
+
@signature_fields = []
|
28
|
+
@tags = []
|
29
|
+
|
30
|
+
#parse unless @raw.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Raw documentation text.
|
34
|
+
#
|
35
|
+
# Returns String of raw documentation text.
|
36
|
+
def to_s
|
37
|
+
@raw
|
38
|
+
end
|
39
|
+
|
40
|
+
# Validate given comment text.
|
41
|
+
#
|
42
|
+
# Returns true if comment is valid, otherwise false.
|
43
|
+
def self.valid?(text)
|
44
|
+
new(text).valid?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Validate raw comment.
|
48
|
+
#
|
49
|
+
# TODO: This needs improvement.
|
50
|
+
#
|
51
|
+
# Returns true if comment is valid, otherwise false.
|
52
|
+
def valid?
|
53
|
+
begin
|
54
|
+
new(text).validate
|
55
|
+
true
|
56
|
+
rescue ParseError
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Validate raw comment.
|
62
|
+
#
|
63
|
+
# Returns true if comment is valid.
|
64
|
+
# Raises ParseError if comment is not valid.
|
65
|
+
def validate
|
66
|
+
if !raw.include?('Returns')
|
67
|
+
raise ParseError.new("No `Returns' statement.")
|
68
|
+
end
|
69
|
+
|
70
|
+
if sections.size < 2
|
71
|
+
raise ParseError.new("No description section found.")
|
72
|
+
end
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
# The raw comment text cleaned-up and ready for section parsing.
|
78
|
+
#
|
79
|
+
# Returns cleaned-up comment String.
|
80
|
+
def tomdoc
|
81
|
+
lines = raw.split("\n")
|
82
|
+
|
83
|
+
# remove remark symbol
|
84
|
+
if lines.all?{ |line| /^\s*#/ =~ line }
|
85
|
+
lines = lines.map do |line|
|
86
|
+
line =~ /^(\s*#)/ ? line.sub($1, '') : nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# for some reason the first line is coming in without indention
|
91
|
+
# regardless, so we temporary remove it
|
92
|
+
first = lines.shift
|
93
|
+
|
94
|
+
# remove indention
|
95
|
+
spaces = lines.map do |line|
|
96
|
+
next if line.strip.empty?
|
97
|
+
md = /^(\s*)/.match(line)
|
98
|
+
md ? md[1].size : nil
|
99
|
+
end.compact
|
100
|
+
|
101
|
+
space = spaces.min || 0
|
102
|
+
lines = lines.map do |line|
|
103
|
+
if line.strip.empty?
|
104
|
+
line.strip
|
105
|
+
else
|
106
|
+
line[space..-1]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# put first line back
|
111
|
+
lines.unshift(first.sub(/^\s*/,'')) if first
|
112
|
+
|
113
|
+
lines.compact.join("\n")
|
114
|
+
end
|
115
|
+
|
116
|
+
# List of comment sections. These are divided simply on "\n\n".
|
117
|
+
#
|
118
|
+
# Returns Array of comment sections.
|
119
|
+
def sections
|
120
|
+
parsed {
|
121
|
+
@sections
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
# Description of method or class/module.
|
126
|
+
#
|
127
|
+
# Returns description String.
|
128
|
+
def description
|
129
|
+
parsed {
|
130
|
+
@description
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Arguments list.
|
135
|
+
#
|
136
|
+
# Returns list of arguments.
|
137
|
+
def arguments
|
138
|
+
parsed {
|
139
|
+
@arguments
|
140
|
+
}
|
141
|
+
end
|
142
|
+
alias args arguments
|
143
|
+
|
144
|
+
# Keyword arguments, aka Options.
|
145
|
+
#
|
146
|
+
# Returns list of options.
|
147
|
+
def options
|
148
|
+
parsed {
|
149
|
+
@options
|
150
|
+
}
|
151
|
+
end
|
152
|
+
alias keyword_arguments options
|
153
|
+
|
154
|
+
# List of use examples of a method or class/module.
|
155
|
+
#
|
156
|
+
# Returns String of examples.
|
157
|
+
def examples
|
158
|
+
parsed {
|
159
|
+
@examples
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
# Description of a methods yield procedure.
|
164
|
+
#
|
165
|
+
# Returns String decription of yield procedure.
|
166
|
+
def yields
|
167
|
+
parsed {
|
168
|
+
@yields
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
# The list of retrun values a method can return.
|
173
|
+
#
|
174
|
+
# Returns Array of method return descriptions.
|
175
|
+
def returns
|
176
|
+
parsed {
|
177
|
+
@returns
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
# A list of errors a method might raise.
|
182
|
+
#
|
183
|
+
# Returns Array of method raises descriptions.
|
184
|
+
def raises
|
185
|
+
parsed {
|
186
|
+
@raises
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
# A list of alternate method signatures.
|
191
|
+
#
|
192
|
+
# Returns Array of signatures.
|
193
|
+
def signatures
|
194
|
+
parsed {
|
195
|
+
@signatures
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
# A list of signature fields.
|
200
|
+
#
|
201
|
+
# Returns Array of field definitions.
|
202
|
+
def signature_fields
|
203
|
+
parsed {
|
204
|
+
@signature_fields
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
# List of tags.
|
209
|
+
#
|
210
|
+
# Returns an associatve array of tags. [Array<Array<String>>]
|
211
|
+
def tags
|
212
|
+
parsed {
|
213
|
+
@tags
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
# Method status, can be `Public`, `Internal` or `Deprecated`.
|
218
|
+
#
|
219
|
+
# Returns [String]
|
220
|
+
def status
|
221
|
+
parsed {
|
222
|
+
@status
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
# Check if method is public.
|
227
|
+
#
|
228
|
+
# Returns true if method is public.
|
229
|
+
def public?
|
230
|
+
parsed {
|
231
|
+
@status == 'Public'
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
# Check if method is internal.
|
236
|
+
#
|
237
|
+
# Returns true if method is internal.
|
238
|
+
def internal?
|
239
|
+
parsed {
|
240
|
+
@status == 'Internal'
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
# Check if method is deprecated.
|
245
|
+
#
|
246
|
+
# Returns true if method is deprecated.
|
247
|
+
def deprecated?
|
248
|
+
parsed {
|
249
|
+
@status == 'Deprecated'
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
=begin
|
254
|
+
# Internal: Parse the Tomdoc formatted comment.
|
255
|
+
#
|
256
|
+
# Returns true if there was a comment to parse.
|
257
|
+
def parse
|
258
|
+
@parsed = true
|
259
|
+
|
260
|
+
sections = tomdoc.split("\n\n")
|
261
|
+
|
262
|
+
return false if sections.empty?
|
263
|
+
|
264
|
+
# The description is always the first section, but it may have
|
265
|
+
# multiple paragraphs. This routine collects those together.
|
266
|
+
desc = [sections.shift]
|
267
|
+
loop do
|
268
|
+
s = sections.first
|
269
|
+
break if s.nil? # got nothing
|
270
|
+
break if s =~ /^\w+\s+\-/m # argument line
|
271
|
+
break if section_type(s) != nil # another section type
|
272
|
+
desc << sections.shift
|
273
|
+
end
|
274
|
+
sections = [desc.join("\n\n")] + sections
|
275
|
+
|
276
|
+
@sections = sections.dup
|
277
|
+
|
278
|
+
parse_description(sections.shift)
|
279
|
+
|
280
|
+
if sections.first && sections.first =~ /^\w+\s+\-/m
|
281
|
+
parse_arguments(sections.shift)
|
282
|
+
end
|
283
|
+
|
284
|
+
current = sections.shift
|
285
|
+
while current
|
286
|
+
case type = section_type(current)
|
287
|
+
#when :arguments
|
288
|
+
# parse_arguments(current)
|
289
|
+
#when :options
|
290
|
+
# parse_options(current)
|
291
|
+
when :examples
|
292
|
+
parse_examples(current, sections)
|
293
|
+
when :yields
|
294
|
+
parse_yields(current)
|
295
|
+
when :returns
|
296
|
+
parse_returns(current) # also does raises
|
297
|
+
when :raises
|
298
|
+
parse_returns(current) # also does returns
|
299
|
+
when :signature
|
300
|
+
parse_signature(current, sections)
|
301
|
+
when Symbol
|
302
|
+
parse_tag(current)
|
303
|
+
end
|
304
|
+
current = sections.shift
|
305
|
+
end
|
306
|
+
|
307
|
+
return @parsed
|
308
|
+
end
|
309
|
+
=end
|
310
|
+
|
311
|
+
# Internal: Parse the Tomdoc formatted comment.
|
312
|
+
#
|
313
|
+
# Returns true if there was a comment to parse.
|
314
|
+
def parse
|
315
|
+
@parsed = true
|
316
|
+
|
317
|
+
sections = smart_split(tomdoc)
|
318
|
+
|
319
|
+
return false if sections.empty?
|
320
|
+
|
321
|
+
# We are assuming that the first section is always description.
|
322
|
+
# And it should be, but people aren't always proper, so perhaps
|
323
|
+
# this can be made a little smarter in the future.
|
324
|
+
parse_description(sections.shift)
|
325
|
+
|
326
|
+
# The second section may be arguments.
|
327
|
+
if sections.first && sections.first =~ /^\w+\s+\-/m
|
328
|
+
parse_arguments(sections.shift)
|
329
|
+
end
|
330
|
+
|
331
|
+
current = sections.shift
|
332
|
+
while current
|
333
|
+
case type = section_type(current)
|
334
|
+
when :arguments
|
335
|
+
parse_arguments(current)
|
336
|
+
when :options
|
337
|
+
parse_options(current)
|
338
|
+
when :example
|
339
|
+
parse_example(current)
|
340
|
+
when :examples
|
341
|
+
parse_examples(current)
|
342
|
+
when :yields
|
343
|
+
parse_yields(current)
|
344
|
+
when :returns
|
345
|
+
parse_returns(current)
|
346
|
+
when :raises
|
347
|
+
parse_raises(current)
|
348
|
+
when :signature
|
349
|
+
parse_signature(current)
|
350
|
+
when Symbol
|
351
|
+
parse_tag(current)
|
352
|
+
end
|
353
|
+
current = sections.shift
|
354
|
+
end
|
355
|
+
|
356
|
+
return @parsed
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
# Has the comment been parsed yet?
|
362
|
+
def parsed(&block)
|
363
|
+
parse unless @parsed
|
364
|
+
block.call
|
365
|
+
end
|
366
|
+
|
367
|
+
# Split the documentation up into proper sections.
|
368
|
+
#
|
369
|
+
# Returns an array section strings. [Array<String>]
|
370
|
+
def smart_split(doc)
|
371
|
+
splits = []
|
372
|
+
index = -1
|
373
|
+
|
374
|
+
lines = doc.lines.to_a
|
375
|
+
|
376
|
+
# Remove any blank lines off the top.
|
377
|
+
lines.shift while lines.first && lines.first.strip.empty?
|
378
|
+
|
379
|
+
# Keep a copy of the lines for later use.
|
380
|
+
doc_lines = lines.dup
|
381
|
+
|
382
|
+
# The first line may have a `Public`/`Private`/`Deprecated` marker.
|
383
|
+
# We need to remove that for the moment to properly check for
|
384
|
+
# subsequent labeled sections.
|
385
|
+
#first = lines.first.dup
|
386
|
+
#lines.first.sub(/^[A-Z]\w+\:\s*/, '')
|
387
|
+
|
388
|
+
# The description is always the first section, but it may have
|
389
|
+
# multiple paragraphs. And the second section may be an arguments
|
390
|
+
# list without a header. This loop handles that.
|
391
|
+
while line = lines.shift
|
392
|
+
index += 1
|
393
|
+
if argument_line?(line)
|
394
|
+
splits << index
|
395
|
+
break
|
396
|
+
elsif section_type(line)
|
397
|
+
splits << index
|
398
|
+
break
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# The rest of the the document should have identifiable section markers.
|
403
|
+
while line = lines.shift
|
404
|
+
index += 1
|
405
|
+
if section_type(line)
|
406
|
+
splits << index
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
# Now we split the documentation up into sections using
|
411
|
+
# the line indexes we collected above.
|
412
|
+
sections = []
|
413
|
+
b = 0
|
414
|
+
splits.shift if splits.first == 0
|
415
|
+
splits.each do |i|
|
416
|
+
sections << doc_lines[b...i].join
|
417
|
+
b = i
|
418
|
+
end
|
419
|
+
sections << doc_lines[b..-1].join
|
420
|
+
|
421
|
+
return sections
|
422
|
+
end
|
423
|
+
|
424
|
+
# Check if a line of text could be an argument definition.
|
425
|
+
# I.e. it has a word followed by a dash.
|
426
|
+
#
|
427
|
+
# Return [Boolean]
|
428
|
+
def argument_line?(line)
|
429
|
+
/^\w+\s+\-/m =~ line.strip
|
430
|
+
end
|
431
|
+
|
432
|
+
# Determine section type.
|
433
|
+
def section_type(section)
|
434
|
+
case section
|
435
|
+
when /\AArguments\s*$/
|
436
|
+
:arguments
|
437
|
+
when /\AOptions\s*$/
|
438
|
+
:options
|
439
|
+
when /\AExamples\s*$/
|
440
|
+
:examples
|
441
|
+
when /\AExample\s*$/
|
442
|
+
:example
|
443
|
+
when /\ASignature(s)?\s*$/
|
444
|
+
:signature
|
445
|
+
when /^Yield(s)?/
|
446
|
+
:yields
|
447
|
+
when /^Return(s)?/
|
448
|
+
:returns
|
449
|
+
when /^Raise(s)?/
|
450
|
+
:raises
|
451
|
+
when /\A([A-Z]\w+)\:\ /
|
452
|
+
$1.to_sym
|
453
|
+
else
|
454
|
+
nil
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
# Recognized description status.
|
459
|
+
TOMDOC_STATUS = ['Internal', 'Public', 'Deprecated']
|
460
|
+
|
461
|
+
# Parse description.
|
462
|
+
#
|
463
|
+
# section - String containig description.
|
464
|
+
#
|
465
|
+
# Returns nothing.
|
466
|
+
def parse_description(section)
|
467
|
+
if md = /^([A-Z]\w+\:)/.match(section)
|
468
|
+
@status = md[1].chomp(':')
|
469
|
+
if TOMDOC_STATUS.include?(@status)
|
470
|
+
@description = md.post_match.strip
|
471
|
+
else
|
472
|
+
@description = section.strip
|
473
|
+
end
|
474
|
+
else
|
475
|
+
@description = section.strip
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Parse arguments section. Arguments occur subsequent to
|
480
|
+
# the description.
|
481
|
+
#
|
482
|
+
# section - String containing argument definitions.
|
483
|
+
#
|
484
|
+
# Returns nothing.
|
485
|
+
def parse_arguments(section)
|
486
|
+
args = []
|
487
|
+
last_indent = nil
|
488
|
+
|
489
|
+
section.lines.each do |line|
|
490
|
+
next if /^Arguments\s*$/i =~ line # optional header
|
491
|
+
next if line.strip.empty?
|
492
|
+
indent = line.scan(/^\s*/)[0].to_s.size
|
493
|
+
|
494
|
+
if last_indent && indent >= last_indent
|
495
|
+
args.last.description << "\r\n" + line
|
496
|
+
else
|
497
|
+
param, desc = line.split(" - ")
|
498
|
+
args << Argument.new(param.strip, desc.to_s.strip) if param #&& desc
|
499
|
+
last_indent = indent + 1
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
args.each do |arg|
|
504
|
+
arg.parse(arg.description)
|
505
|
+
end
|
506
|
+
|
507
|
+
@arguments = args
|
508
|
+
end
|
509
|
+
|
510
|
+
# the description.
|
511
|
+
#
|
512
|
+
# section - String containing argument definitions.
|
513
|
+
#
|
514
|
+
# Returns nothing.
|
515
|
+
def parse_options(section)
|
516
|
+
opts = []
|
517
|
+
last_indent = nil
|
518
|
+
|
519
|
+
section.lines.each do |line|
|
520
|
+
next if /^\s*Options\s*$/i =~ line # optional header
|
521
|
+
next if line.strip.empty?
|
522
|
+
indent = line.scan(/^\s*/)[0].to_s.size
|
523
|
+
|
524
|
+
if last_indent && indent > 0 && indent >= last_indent
|
525
|
+
opts.last.description << "\r\n" + line
|
526
|
+
else
|
527
|
+
param, desc = line.split(" - ")
|
528
|
+
opts << Option.new(param.strip, desc.strip) if param && desc
|
529
|
+
end
|
530
|
+
|
531
|
+
last_indent = indent
|
532
|
+
end
|
533
|
+
|
534
|
+
#opts.each do |opt|
|
535
|
+
# opt.parse(arg.description)
|
536
|
+
#end
|
537
|
+
|
538
|
+
@options = opts
|
539
|
+
end
|
540
|
+
|
541
|
+
# Parse example.
|
542
|
+
#
|
543
|
+
# section - String starting with `Examples`.
|
544
|
+
# sections - All sections subsequent to section.
|
545
|
+
#
|
546
|
+
# Returns nothing.
|
547
|
+
def parse_example(section)
|
548
|
+
examples = []
|
549
|
+
|
550
|
+
# TODO: make the unidention smarter (could be more than 2 spaces)
|
551
|
+
section = section.sub('Example', '').gsub(/^\s{2}/,'')
|
552
|
+
|
553
|
+
@examples << section unless section.strip.empty?
|
554
|
+
end
|
555
|
+
|
556
|
+
# Parse examples.
|
557
|
+
#
|
558
|
+
# section - String starting with `Examples`.
|
559
|
+
# sections - All sections subsequent to section.
|
560
|
+
#
|
561
|
+
# Returns nothing.
|
562
|
+
def parse_examples(section)
|
563
|
+
#examples = []
|
564
|
+
|
565
|
+
# TODO: make the unidention smarter (could be more than 2 spaces)
|
566
|
+
section = section.sub('Examples', '')
|
567
|
+
|
568
|
+
section.split("\n\n").each do |ex|
|
569
|
+
@examples << ex.gsub(/^\s{2}/,'') unless ex.strip.empty?
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
# Parse yields section.
|
574
|
+
#
|
575
|
+
# section - String contaning Yields line.
|
576
|
+
#
|
577
|
+
# Returns nothing.
|
578
|
+
def parse_yields(section)
|
579
|
+
@yields = section.strip
|
580
|
+
end
|
581
|
+
|
582
|
+
# Parse returns section.
|
583
|
+
#
|
584
|
+
# section - String contaning Returns and/or Raises lines.
|
585
|
+
#
|
586
|
+
# Returns nothing.
|
587
|
+
def parse_returns(section)
|
588
|
+
text = section.gsub(/\s+/, ' ').strip
|
589
|
+
@returns << text
|
590
|
+
|
591
|
+
#returns, raises, current = [], [], []
|
592
|
+
#
|
593
|
+
#lines = section.split("\n")
|
594
|
+
#lines.each do |line|
|
595
|
+
# case line
|
596
|
+
# when /^Returns/
|
597
|
+
# returns << line
|
598
|
+
# current = returns
|
599
|
+
# when /^Raises/
|
600
|
+
# raises << line
|
601
|
+
# current = raises
|
602
|
+
# when /^\s+/
|
603
|
+
# current.last << line.squeeze(' ')
|
604
|
+
# else
|
605
|
+
# current << line # TODO: What to do with non-compliant line?
|
606
|
+
# end
|
607
|
+
#end
|
608
|
+
#
|
609
|
+
#@returns.concat(returns)
|
610
|
+
#@raises.concat(raises)
|
611
|
+
end
|
612
|
+
|
613
|
+
# Parse raises section.
|
614
|
+
#
|
615
|
+
# section - String contaning Raises text.
|
616
|
+
#
|
617
|
+
# Returns nothing.
|
618
|
+
def parse_raises(section)
|
619
|
+
text = section.gsub(/\s+/, ' ').strip
|
620
|
+
@raises << text.strip
|
621
|
+
end
|
622
|
+
|
623
|
+
# Parse signature section.
|
624
|
+
#
|
625
|
+
# IMPORTANT! This is not mojombo TomDoc! Rather signatures are simply
|
626
|
+
# a list of alternate ways to call a method, e.g. when *args is used but
|
627
|
+
# only specific argument patterns are possible.
|
628
|
+
#
|
629
|
+
# section - String starting with `Signature`.
|
630
|
+
#
|
631
|
+
# Returns nothing.
|
632
|
+
def parse_signature(section)
|
633
|
+
signatures = []
|
634
|
+
|
635
|
+
section = section.sub(/^\s*Signature(s)?/, '').strip
|
636
|
+
|
637
|
+
lines = section.lines.to_a
|
638
|
+
|
639
|
+
lines.each do |line|
|
640
|
+
next if line.strip.empty?
|
641
|
+
signatures << line.strip
|
642
|
+
end
|
643
|
+
|
644
|
+
@signatures = signatures
|
645
|
+
|
646
|
+
#if line =~ /^\w+\s*\-/m
|
647
|
+
# parse_signature_fields(sections.shift)
|
648
|
+
#end
|
649
|
+
end
|
650
|
+
|
651
|
+
=begin
|
652
|
+
# Subsequent to Signature section there can be field
|
653
|
+
# definitions.
|
654
|
+
#
|
655
|
+
# section - String subsequent to signatures.
|
656
|
+
#
|
657
|
+
# Returns nothing.
|
658
|
+
def parse_signature_fields(section)
|
659
|
+
args = []
|
660
|
+
last_indent = nil
|
661
|
+
|
662
|
+
section.split("\n").each do |line|
|
663
|
+
next if line.strip.empty?
|
664
|
+
indent = line.scan(/^\s*/)[0].to_s.size
|
665
|
+
|
666
|
+
if last_indent && indent > last_indent
|
667
|
+
args.last.description << line.squeeze(" ")
|
668
|
+
else
|
669
|
+
param, desc = line.split(" - ")
|
670
|
+
args << Argument.new(param.strip, desc.strip) if param && desc
|
671
|
+
end
|
672
|
+
|
673
|
+
last_indent = indent
|
674
|
+
end
|
675
|
+
|
676
|
+
@signature_fields = args
|
677
|
+
end
|
678
|
+
=end
|
679
|
+
|
680
|
+
# Tags are arbitrary sections designated by a capitalized label and a colon.
|
681
|
+
#
|
682
|
+
# label - String name of the tag.
|
683
|
+
# section - String of the tag section.
|
684
|
+
#
|
685
|
+
# Returns nothing.
|
686
|
+
def parse_tag(section)
|
687
|
+
md = /^([A-Z]\w+)\:\ /m.match(section)
|
688
|
+
|
689
|
+
label = md[1]
|
690
|
+
desc = md.post_match
|
691
|
+
|
692
|
+
warn "No label?" unless label
|
693
|
+
|
694
|
+
@tags << [label, desc.strip] if label
|
695
|
+
end
|
696
|
+
|
697
|
+
end
|
698
|
+
|
699
|
+
end
|