docurium 0.0.5 → 0.1.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.
@@ -0,0 +1,427 @@
1
+ class Docurium
2
+ class CParser
3
+
4
+ # Remove common prefix of all lines in comment.
5
+ # Otherwise tries to preserve formatting in case it is relevant.
6
+ def cleanup_comment(comment)
7
+ return "" unless comment
8
+
9
+ lines = 0
10
+ prefixed = 0
11
+ shortest = nil
12
+
13
+ compacted = comment.sub(/^\n+/,"").sub(/\n\s*$/, "\n")
14
+
15
+ compacted.split(/\n/).each do |line|
16
+ lines += 1
17
+ if line =~ /^\s*\*\s*$/ || line =~ /^\s*$/
18
+ # don't count length of blank lines or lines with just a " * " on
19
+ # them towards shortest common prefix
20
+ prefixed += 1
21
+ shortest = line if shortest.nil?
22
+ elsif line =~ /^(\s*\*\s*)/
23
+ prefixed += 1
24
+ shortest = $1 if shortest.nil? || shortest.length > $1.length
25
+ end
26
+ end
27
+
28
+ if shortest =~ /\s$/
29
+ shortest = Regexp.quote(shortest.chop) + "[ \t]"
30
+ elsif shortest
31
+ shortest = Regexp.quote(shortest)
32
+ end
33
+
34
+ if lines == prefixed && !shortest.nil? && shortest.length > 0
35
+ if shortest =~ /\*/
36
+ return comment.gsub(/^#{shortest}/, "").gsub(/^\s*\*\s*\n/, "\n")
37
+ else
38
+ return comment.gsub(/^#{shortest}/, "")
39
+ end
40
+ else
41
+ return comment
42
+ end
43
+ end
44
+
45
+ # Find the shortest common prefix of an array of strings
46
+ def shortest_common_prefix(arr)
47
+ arr.inject do |pfx,str|
48
+ pfx = pfx.chop while pfx != str[0...pfx.length]; pfx
49
+ end
50
+ end
51
+
52
+ # Match #define A(B) or #define A
53
+ # and convert a series of simple #defines into an enum
54
+ def detect_define(d)
55
+ if d[:body] =~ /\A\#\s*define\s+((\w+)\([^\)]+\))/
56
+ d[:type] = :macro
57
+ d[:decl] = $1.strip
58
+ d[:name] = $2
59
+ d[:tdef] = nil
60
+ elsif d[:body] =~ /\A\#\s*define\s+(\w+)/
61
+ names = []
62
+ d[:body].scan(/\#\s*define\s+(\w+)/) { |m| names << m[0].to_s }
63
+ d[:tdef] = nil
64
+ names.uniq!
65
+ if names.length == 1
66
+ d[:type] = :define
67
+ d[:decl] = names[0]
68
+ d[:name] = names[0]
69
+ elsif names.length > 1
70
+ d[:type] = :enum
71
+ d[:decl] = names
72
+ d[:name] = shortest_common_prefix(names)
73
+ d[:name].sub!(/_*$/, '')
74
+ end
75
+ end
76
+ end
77
+
78
+ # Take a multi-line #define and join into a simple definition
79
+ def join_define(text)
80
+ text = text.split("\n\n", 2).first || ""
81
+
82
+ # Ruby 1.8 does not support negative lookbehind regex so let's
83
+ # get the joined macro definition a slightly more awkward way
84
+ text.split(/\s*\n\s*/).inject("\\") do |val, line|
85
+ (val[-1] == ?\\) ? val = val.chop.strip + " " + line : val
86
+ end.strip.gsub(/^\s*\\*\s*/, '')
87
+ end
88
+
89
+ # Process #define A(B) macros
90
+ def parse_macro(d)
91
+ if d[:body] =~ /define\s+#{Regexp.quote(d[:name])}\([^\)]*\)[ \t]*(.*)/m
92
+ d[:value] = join_define($1)
93
+ end
94
+ d[:comments] = d[:rawComments].strip
95
+ end
96
+
97
+ # Process #define A ... macros
98
+ def parse_define(d)
99
+ if d[:body] =~ /define\s+#{Regexp.quote(d[:name])}[ \t]*(.*)/
100
+ d[:value] = join_define($1)
101
+ end
102
+ d[:comments] = d[:rawComments].strip
103
+ end
104
+
105
+ # Match enum {} (and possibly a typedef thereof)
106
+ def detect_enum(d)
107
+ if d[:body] =~ /\A(typedef)?\s*enum\s*\{([^\}]+)\}\s*([^;]+)?;/i
108
+ typedef, values, name = $1, $2, $3
109
+ d[:type] = :enum
110
+ d[:decl] = values.strip.split(/\s*,\s*/).map do |v|
111
+ v.split(/\s*=\s*/)[0].strip
112
+ end
113
+ if typedef.nil?
114
+ d[:name] = shortest_common_prefix(d[:decl])
115
+ d[:name].sub!(/_*$/, '')
116
+ # Using the common prefix for grouping enum values is a little
117
+ # overly aggressive in some cases. If we ended up with too short
118
+ # a prefix or a prefix which is too generic, then skip it.
119
+ d[:name] = nil unless d[:name].scan('_').length > 1
120
+ else
121
+ d[:name] = name
122
+ end
123
+ d[:tdef] = typedef
124
+ end
125
+ end
126
+
127
+ # Process enum definitions
128
+ def parse_enum(d)
129
+ if d[:decl].respond_to? :map
130
+ d[:block] = d[:decl].map { |v| v.strip }.join("\n")
131
+ else
132
+ d[:block] = d[:decl]
133
+ end
134
+ d[:comments] = d[:rawComments].strip
135
+ end
136
+
137
+ # Match struct {} (and typedef thereof) or opaque struct typedef
138
+ def detect_struct(d)
139
+ if d[:body] =~ /\A(typedef)?\s*struct\s*(\w+)?\s*\{([^\}]+)\}\s*([^;]+)?/i
140
+ typedef, name1, fields, name2 = $1, $2, $3, $4
141
+ d[:type] = :struct
142
+ d[:name] = typedef.nil? ? name1 : name2;
143
+ d[:tdef] = typedef
144
+ d[:decl] = fields.strip.split(/\s*\;\s*/).map do |x|
145
+ x.strip.gsub(/\s+/, " ").gsub(/\(\s+/,"(")
146
+ end
147
+ elsif d[:body] =~ /\A(typedef)\s+struct\s+\w+\s+(\w+)/
148
+ d[:type] = :struct
149
+ d[:decl] = ""
150
+ d[:name] = $2
151
+ d[:tdef] = $1
152
+ end
153
+ end
154
+
155
+ # Process struct definition
156
+ def parse_struct(d)
157
+ if d[:decl].respond_to? :map
158
+ d[:block] = d[:decl].map { |v| v.strip }.join("\n")
159
+ else
160
+ d[:block] = d[:decl]
161
+ end
162
+ d[:comments] = d[:rawComments].strip
163
+ end
164
+
165
+ # Match other typedefs, checking explicitly for function pointers
166
+ # but otherwise just trying to extract a name as simply as possible.
167
+ def detect_typedef(d)
168
+ if d[:body] =~ /\Atypedef\s+([^;]+);/
169
+ d[:decl] = $1.strip
170
+ if d[:decl] =~ /\S+\s+\(\*([^\)]+)\)\(/
171
+ d[:type] = :fnptr
172
+ d[:name] = $1
173
+ else
174
+ d[:type] = :typedef
175
+ d[:name] = d[:decl].split(/\s+/).last
176
+ end
177
+ end
178
+ end
179
+
180
+ # Process typedef definition
181
+ def parse_typedef(d)
182
+ d[:comments] = d[:rawComments].strip
183
+ end
184
+
185
+ # Process function pointer typedef definition
186
+ def parse_fnptr(d)
187
+ d[:comments] = d[:rawComments].strip
188
+ end
189
+
190
+ # Match function prototypes or inline function declarations
191
+ def detect_function(d)
192
+ if d[:body] =~ /[;\{]/
193
+ d[:type] = :file
194
+ d[:decl] = ""
195
+
196
+ proto = d[:body].split(/[;\{]/, 2).first.strip
197
+ if proto[-1] == ?)
198
+ (proto.length - 1).downto(0) do |p|
199
+ tail = proto[p .. -1]
200
+ if tail.count(")") == tail.count("(")
201
+ if proto[0..p] =~ /(\w+)\(\z/
202
+ d[:name] = $1
203
+ d[:type] = :function
204
+ d[:decl] = proto
205
+ end
206
+ break
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ # Process function prototype and comments
214
+ def parse_function(d)
215
+ d[:args] = []
216
+
217
+ rval, argline = d[:decl].split(/\s*#{Regexp.quote(d[:name])}\s*/, 2)
218
+
219
+ # clean up rval if it is like "extern static int" or "GIT_EXTERN(int)"
220
+ while rval =~ /[A-Za-z0-9_]+\(([^\)]+)\)$/i
221
+ rval = $1
222
+ end
223
+ rval.gsub!(/extern|static/, '')
224
+ rval.strip!
225
+ d[:return] = { :type => rval }
226
+
227
+ # clean up argline
228
+ argline = argline.slice(1..-2) while argline[0] == ?( && argline[-1] == ?)
229
+ d[:argline] = argline.strip
230
+ d[:args] = []
231
+ left = 0
232
+
233
+ # parse argline
234
+ (0 .. argline.length).each do |i|
235
+ next unless argline[i] == ?, || argline.length == i
236
+
237
+ s = argline.slice(left .. i)
238
+ next unless s.count("(") == s.count(")")
239
+
240
+ s.chop! if argline[i] == ?,
241
+ s.strip!
242
+
243
+ if s =~ /\(\s*\*\s*(\w+)\s*\)\s*\(/
244
+ argname = $1
245
+ d[:args] << {
246
+ :name => argname,
247
+ :type => s.sub(/\s*#{Regexp.quote(argname)}\s*/, '').strip
248
+ }
249
+ elsif s =~ /\W(\w+)$/
250
+ argname = $1
251
+ d[:args] << {
252
+ :name => argname,
253
+ :type => s[0 ... - argname.length].strip,
254
+ }
255
+ else
256
+ # argline is probably something like "(void)"
257
+ end
258
+
259
+ left = i + 1
260
+ end
261
+
262
+ # parse comments
263
+ if d[:rawComments] =~ /\@(param|return)/i
264
+ d[:args].each do |arg|
265
+ param_comment = /\@param\s+#{Regexp.quote(arg[:name])}/.match(d[:rawComments])
266
+ if param_comment
267
+ after = param_comment.post_match
268
+ end_comment = after.index(/(?:@param|@return|\Z)/)
269
+ arg[:comment] = after[0 ... end_comment].strip.gsub(/\s+/, ' ')
270
+ end
271
+ end
272
+
273
+ return_comment = /\@return\s+/.match(d[:rawComments])
274
+ if return_comment
275
+ after = return_comment.post_match
276
+ d[:return][:comment] = after[0 ... after.index(/(?:@param|\Z)/)].strip.gsub(/\s+/, ' ')
277
+ end
278
+ else
279
+ # support for TomDoc params
280
+ end
281
+
282
+ # add in inline parameter comments
283
+ if d[:inlines] # array of [param line]/[comment] pairs
284
+ d[:inlines].each do |inl|
285
+ d[:args].find do |arg|
286
+ if inl[0] =~ /\b#{Regexp.quote(arg[:name])}$/
287
+ arg[:comment] += "\n#{inl[1]}"
288
+ end
289
+ end
290
+ end
291
+ end
292
+
293
+ # generate function signature
294
+ d[:sig] = d[:args].map { |a| a[:type].to_s }.join('::')
295
+
296
+ # pull off function description
297
+ if d[:rawComments] =~ /^\s*(public|internal|deprecated):/i
298
+ # support for TomDoc description
299
+ else
300
+ desc, comments = d[:rawComments].split("\n\n", 2)
301
+ d[:description] = desc.strip
302
+ d[:comments] = comments || ""
303
+ params_start = d[:comments].index(/\s?\@(?:param|return)/)
304
+ d[:comments] = d[:comments].slice(0, params_start) if params_start
305
+ end
306
+ end
307
+
308
+ # Match otherwise unrecognized commented blocks
309
+ def detect_catchall(d)
310
+ d[:type] = :file
311
+ d[:decl] = ""
312
+ end
313
+
314
+ # Process comment blocks that are only associated with the whole file.
315
+ def parse_file(d)
316
+ m = []
317
+ d[:brief] = m[1] if m = /@brief (.*?)$/.match(d[:rawComments])
318
+ d[:defgroup] = m[1] if m = /@defgroup (.*?)$/.match(d[:rawComments])
319
+ d[:ingroup] = m[1] if m = /@ingroup (.*?)$/.match(d[:rawComments])
320
+ comments = d[:rawComments].gsub(/^@.*$/, '').strip + "\n"
321
+ if d[:comments]
322
+ d[:comments] = d[:comments].strip + "\n\n" + comments
323
+ else
324
+ d[:comments] = comments
325
+ end
326
+ end
327
+
328
+ # Array of detectors to execute in order
329
+ DETECTORS = %w(define enum struct typedef function catchall)
330
+
331
+ # Given a commented chunk of file, try to parse it.
332
+ def parse_declaration_block(d)
333
+ # skip uncommented declarations
334
+ return unless d[:rawComments].length > 0
335
+
336
+ # remove inline comments in declaration
337
+ while comment = d[:body].index("/*") do
338
+ end_comment = d[:body].index("*/", comment)
339
+ d[:body].slice!(comment, end_comment - comment + 2)
340
+ end
341
+
342
+ # if there are multiple #ifdef'ed declarations, we'll just
343
+ # strip out the #if/#ifdef and use the first one
344
+ d[:body].sub!(/[^\n]+\n/, '') if d[:body] =~ /\A\#\s*if/
345
+
346
+ # try detectors until one assigns a :type to the declaration
347
+ # it's going to be one of:
348
+ # - :define -> #defines + convert a series of simple ones to :enum
349
+ # - :enum -> (typedef)? enum { ... };
350
+ # - :struct -> (typedef)? struct { ... };
351
+ # - :fnptr -> typedef x (*fn)(...);
352
+ # - :typedef -> typedef x y; (not enum, struct, fnptr)
353
+ # - :function -> rval something(like this);
354
+ # - :file -> everything else goes to "file" scope
355
+ DETECTORS.find { |p| method("detect_#{p}").call(d); d.has_key?(:type) }
356
+
357
+ # if we detected something, call a parser for that type of thing
358
+ method("parse_#{d[:type]}").call(d) if d[:type]
359
+ end
360
+
361
+ # Parse a chunk of text as a header file
362
+ def parse_text(filename, content)
363
+ # break into comments and non-comments with line numbers
364
+ content = "/** */" + content if content[0..2] != "/**"
365
+ recs = []
366
+ lineno = 1
367
+ openblock = false
368
+
369
+ content.split(/\/\*\*/).each do |chunk|
370
+ c, b = chunk.split(/[ \t]*\*\//, 2)
371
+ next unless c || b
372
+
373
+ lineno += c.scan("\n").length if c
374
+
375
+ # special handling for /**< ... */ inline comments or
376
+ # for /** ... */ inside an open block
377
+ if openblock || c[0] == ?<
378
+ c = c.sub(/^</, '').strip
379
+
380
+ so_far = recs[-1][:body]
381
+ last_line = so_far[ so_far.rindex("\n")+1 .. -1 ].strip.chomp(",").chomp(";")
382
+ if last_line.empty? && b =~ /^([^;]+)\;/ # apply to this line instead
383
+ last_line = $1.strip.chomp(",").chomp(";")
384
+ end
385
+
386
+ if !last_line.empty?
387
+ recs[-1][:inlines] ||= []
388
+ recs[-1][:inlines] << [ last_line, c ]
389
+ if b
390
+ recs[-1][:body] += b
391
+ lineno += b.scan("\n").length
392
+ openblock = false if b =~ /\}/
393
+ end
394
+ next
395
+ end
396
+ end
397
+
398
+ # make comment have a uniform " *" prefix if needed
399
+ if c !~ /\A[ \t]*\n/ && c =~ /^(\s*\*)/
400
+ c = $1 + c
401
+ end
402
+
403
+ # check for unterminated { brace (to handle inline comments later)
404
+ openblock = true if b =~ /\{[^\}]+\Z/
405
+
406
+ recs << {
407
+ :file => filename,
408
+ :line => lineno + (b.start_with?("\n") ? 1 : 0),
409
+ :body => b,
410
+ :rawComments => cleanup_comment(c),
411
+ }
412
+
413
+ lineno += b.scan("\n").length if b
414
+ end
415
+
416
+ # try parsers on each chunk of commented header
417
+ recs.each do |r|
418
+ r[:body].strip!
419
+ r[:rawComments].strip!
420
+ r[:lineto] = r[:line] + r[:body].scan("\n").length
421
+ parse_declaration_block(r)
422
+ end
423
+
424
+ recs
425
+ end
426
+ end
427
+ end
@@ -3,7 +3,11 @@
3
3
  <head>
4
4
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
5
5
  <title>{{ title }}</title>
6
- <link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
6
+ <link rel="stylesheet" href="https://raw.github.com/jashkenas/docco/c640fda9b0b1d09d2b7236f655b3663dc06c63d6/resources/docco.css">
7
+ <style type="text/css">
8
+ a.fnlink {text-decoration: none}
9
+ a.fnlink:hover {text-decoration: underline}
10
+ </style>
7
11
  </head>
8
12
  <body>
9
13
  <div id='container'>
@@ -0,0 +1,3 @@
1
+ class Docurium
2
+ Version = VERSION = '0.1.0'
3
+ end
data/site/js/docurium.js CHANGED
@@ -154,9 +154,8 @@ $(function() {
154
154
  content.append(retdiv)
155
155
 
156
156
  // Show Non-Parsed Function Comments
157
- if (fdata[fname]['comments']) {
158
- content.append($('<pre>').append(fdata[fname]['comments']))
159
- }
157
+ if (fdata[fname]['comments'])
158
+ content.append($('<div>').append(fdata[fname]['comments']))
160
159
 
161
160
  // Show Function Signature
162
161
  ex = $('<code>').addClass('params')
@@ -382,7 +381,11 @@ $(function() {
382
381
  argsText = '( ' + fdata[f]['argline'] + ' )'
383
382
  link = $('<a>').attr('href', '#' + groupLink(gname, f)).append(f)
384
383
  $('.content').append($('<h2>').append(link).append($('<small>').append(argsText)))
385
- $('.content').append($('<pre>').append(fdata[f]['rawComments']))
384
+ description = fdata[f]['description']
385
+ if(fdata[f]['comments'])
386
+ description += "\n\n" + fdata[f]['comments']
387
+
388
+ $('.content').append($('<div>').addClass('description').append(description))
386
389
  }
387
390
  return false
388
391
  },