docurium 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  },