docurium 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 30fc3d67acaf76249929871ddf63e7e3d08d079b
4
+ data.tar.gz: d39f6a4acaf9c0dc2ce836db02969961650e1cd9
5
+ SHA512:
6
+ metadata.gz: 40b673b51c990669d7dff887107b4a43f9318ffa19009b83e18490471bfbc09cfec2b987d69a890b0a566a05a620b5249e2155dab7bed6aaf4df5469f939683f
7
+ data.tar.gz: a90d06b292c137d45cb05f8dc64e98767335514c35af98f348dd4e3ff75ec9d127a163afe779137e8bb85d0137d7f9a125b14e34c996a4018aa1a90c33e22bbe
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - ruby-2.0
4
+ - ruby-2.1
5
+ - ruby-head
6
+ - rbx
7
+
8
+ env:
9
+ - LLVM_CONFIG=llvm-config-3.3
10
+
11
+ before_install:
12
+ - sudo apt-get update
13
+ - sudo apt-get install libclang-3.3-dev llvm-3.3
data/Gemfile CHANGED
@@ -1,5 +1,11 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ platforms :rbx do
4
+ gem 'rubysl', '~> 2.0'
5
+ end
6
+
3
7
  gemspec
4
8
 
9
+ gem 'ffi-clang', :git => 'https://github.com/ioquatix/ffi-clang.git'
10
+
5
11
  # vim:ft=ruby
data/Rakefile CHANGED
@@ -1,7 +1,13 @@
1
1
  require 'rake/testtask'
2
+ require 'rubygems'
3
+ require 'rubygems/package_task'
2
4
 
3
5
  task :default => :test
4
6
 
7
+ gemspec = Gem::Specification::load(File.expand_path('../docurium.gemspec', __FILE__))
8
+ Gem::PackageTask.new(gemspec) do |pkg|
9
+ end
10
+
5
11
  Rake::TestTask.new do |t|
6
12
  t.libs << 'libs' << 'test'
7
13
  t.pattern = 'test/**/*_test.rb'
@@ -13,13 +13,15 @@ Gem::Specification.new do |s|
13
13
  s.description = s.summary
14
14
  s.license = 'MIT'
15
15
 
16
- s.add_dependency "version_sorter", "~>1.1.0"
17
- s.add_dependency "mustache", ">= 0.99.4"
16
+ s.add_dependency "version_sorter", "~>1.1"
17
+ s.add_dependency "mustache", "~> 0.99"
18
18
  s.add_dependency "rocco", "~>0.8"
19
19
  s.add_dependency "gli", "~>2.5"
20
- s.add_dependency "rugged", "~>0.19"
20
+ s.add_dependency "rugged", "~>0.21"
21
21
  s.add_dependency "redcarpet", "~>3.0"
22
+ s.add_dependency "ffi-clang", "~> 0.2"
22
23
  s.add_development_dependency "bundler", "~>1.0"
24
+ s.add_development_dependency "rake", "~> 10.1"
23
25
 
24
26
  s.files = `git ls-files`.split("\n")
25
27
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -4,10 +4,12 @@ require 'version_sorter'
4
4
  require 'rocco'
5
5
  require 'docurium/version'
6
6
  require 'docurium/layout'
7
- require 'docurium/cparser'
7
+ require 'libdetect'
8
+ require 'docurium/docparser'
8
9
  require 'pp'
9
10
  require 'rugged'
10
11
  require 'redcarpet'
12
+ require 'thread'
11
13
 
12
14
  # Markdown expects the old redcarpet compat API, so let's tell it what
13
15
  # to use
@@ -20,19 +22,13 @@ class Docurium
20
22
  raise "You need to specify a config file" if !config_file
21
23
  raise "You need to specify a valid config file" if !valid_config(config_file)
22
24
  @sigs = {}
23
- @groups = {}
24
- if repo
25
- @repo = repo
26
- else
27
- repo_path = Rugged::Repository.discover('.')
28
- @repo = Rugged::Repository.new(repo_path)
29
- end
30
- clear_data
25
+ @repo = repo || Rugged::Repository.discover('.')
31
26
  end
32
27
 
33
- def clear_data(version = 'HEAD')
34
- @data = {:files => [], :functions => {}, :globals => {}, :types => {}, :prefix => ''}
35
- @data[:prefix] = option_version(version, 'input', '')
28
+ def init_data(version = 'HEAD')
29
+ data = {:files => [], :functions => {}, :globals => {}, :types => {}, :prefix => ''}
30
+ data[:prefix] = option_version(version, 'input', '')
31
+ data
36
32
  end
37
33
 
38
34
  def option_version(version, option, default = nil)
@@ -48,81 +44,136 @@ class Docurium
48
44
  opt
49
45
  end
50
46
 
47
+ def format_examples!(data, version)
48
+ examples = []
49
+ if ex = option_version(version, 'examples')
50
+ if subtree = find_subtree(version, ex) # check that it exists
51
+ index = Rugged::Index.new
52
+ index.read_tree(subtree)
53
+
54
+ files = []
55
+ index.each do |entry|
56
+ next unless entry[:path].match(/\.c$/)
57
+ files << entry[:path]
58
+ end
59
+
60
+ files.each do |file|
61
+ # highlight, roccoize and link
62
+ rocco = Rocco.new(file, files, {:language => 'c'}) do
63
+ ientry = index[file]
64
+ blob = @repo.lookup(ientry[:oid])
65
+ blob.content
66
+ end
67
+ rocco_layout = Rocco::Layout.new(rocco, @tf)
68
+ rocco_layout.version = version
69
+ rf = rocco_layout.render
70
+
71
+ extlen = -(File.extname(file).length + 1)
72
+ rf_path = file[0..extlen] + '.html'
73
+ rel_path = "ex/#{version}/#{rf_path}"
74
+
75
+ # look for function names in the examples and link
76
+ id_num = 0
77
+ data[:functions].each do |f, fdata|
78
+ rf.gsub!(/#{f}([^\w])/) do |fmatch|
79
+ extra = $1
80
+ id_num += 1
81
+ name = f + '-' + id_num.to_s
82
+ # save data for cross-link
83
+ data[:functions][f][:examples] ||= {}
84
+ data[:functions][f][:examples][file] ||= []
85
+ data[:functions][f][:examples][file] << rel_path + '#' + name
86
+ "<a name=\"#{name}\" class=\"fnlink\" href=\"../../##{version}/group/#{fdata[:group]}/#{f}\">#{f}</a>#{extra}"
87
+ end
88
+ end
89
+
90
+ # write example to the repo
91
+ sha = @repo.write(rf, :blob)
92
+ examples << [rel_path, sha]
93
+
94
+ data[:examples] ||= []
95
+ data[:examples] << [file, rel_path]
96
+ end
97
+ end
98
+ end
99
+
100
+ examples
101
+ end
102
+
103
+ def generate_doc_for(version)
104
+ index = Rugged::Index.new
105
+ read_subtree(index, version, option_version(version, 'input', ''))
106
+ data = parse_headers(index, version)
107
+ data
108
+ end
109
+
51
110
  def generate_docs
52
- out "* generating docs"
53
111
  output_index = Rugged::Index.new
54
112
  write_site(output_index)
113
+ @tf = File.expand_path(File.join(File.dirname(__FILE__), 'docurium', 'layout.mustache'))
55
114
  versions = get_versions
56
115
  versions << 'HEAD'
116
+ nversions = versions.size
117
+ output = Queue.new
118
+ pipes = {}
57
119
  versions.each do |version|
58
- out " - processing version #{version}"
59
- index = @repo.index
60
- index.clear
61
- clear_data(version)
62
- read_subtree(index, version, @data[:prefix])
63
- parse_headers(index)
64
- tally_sigs(version)
65
-
66
- tf = File.expand_path(File.join(File.dirname(__FILE__), 'docurium', 'layout.mustache'))
67
- if ex = option_version(version, 'examples')
68
- if subtree = find_subtree(version, ex) # check that it exists
69
- index.read_tree(subtree)
70
- out " - processing examples for #{version}"
71
-
72
- files = []
73
- index.each do |entry|
74
- next unless entry[:path].match(/\.c$/)
75
- files << entry[:path]
76
- end
120
+ # We don't need to worry about joining since this process is
121
+ # going to die immediately
122
+ read, write = IO.pipe
123
+ pid = Process.fork do
124
+ read.close
77
125
 
78
- files.each do |file|
79
- out " # #{file}"
126
+ data = generate_doc_for(version)
127
+ examples = format_examples!(data, version)
80
128
 
81
- # highlight, roccoize and link
82
- rocco = Rocco.new(file, files, {:language => 'c'}) do
83
- ientry = index[file]
84
- blob = @repo.lookup(ientry[:oid])
85
- blob.content
86
- end
87
- rocco_layout = Rocco::Layout.new(rocco, tf)
88
- rocco_layout.version = version
89
- rf = rocco_layout.render
90
-
91
- extlen = -(File.extname(file).length + 1)
92
- rf_path = file[0..extlen] + '.html'
93
- rel_path = "ex/#{version}/#{rf_path}"
94
-
95
- # look for function names in the examples and link
96
- id_num = 0
97
- @data[:functions].each do |f, fdata|
98
- rf.gsub!(/#{f}([^\w])/) do |fmatch|
99
- extra = $1
100
- id_num += 1
101
- name = f + '-' + id_num.to_s
102
- # save data for cross-link
103
- @data[:functions][f][:examples] ||= {}
104
- @data[:functions][f][:examples][file] ||= []
105
- @data[:functions][f][:examples][file] << rel_path + '#' + name
106
- "<a name=\"#{name}\" class=\"fnlink\" href=\"../../##{version}/group/#{fdata[:group]}/#{f}\">#{f}</a>#{extra}"
107
- end
108
- end
129
+ Marshal.dump([version, data, examples], write)
130
+ write.close
131
+ end
109
132
 
110
- # write example to the repo
111
- sha = @repo.write(rf, :blob)
112
- output_index.add(:path => rel_path, :oid => sha, :mode => 0100644)
133
+ pipes[pid] = read
134
+ write.close
135
+ end
113
136
 
114
- @data[:examples] ||= []
115
- @data[:examples] << [file, rel_path]
116
- end
117
- end
137
+ print "Generating documentation [0/#{nversions}]\r"
138
+ head_data = nil
139
+
140
+ # This may seem odd, but we need to keep reading from the pipe or
141
+ # the buffer will fill and they'll block and never exit. Therefore
142
+ # we can't rely on Process.wait to tell us when the work is
143
+ # done. Instead read from all the pipes concurrently and send the
144
+ # ruby objects through the queue.
145
+ Thread.abort_on_exception = true
146
+ pipes.each do |pid, read|
147
+ Thread.new do
148
+ result = read.read
149
+ output << Marshal.load(result)
150
+ end
151
+ end
118
152
 
119
- if version == 'HEAD'
120
- show_warnings
121
- end
153
+ for i in 1..nversions
154
+ version, data, examples = output.pop
155
+
156
+ # There's still some work we need to do serially
157
+ tally_sigs!(version, data)
158
+ sha = @repo.write(data.to_json, :blob)
159
+
160
+ print "Generating documentation [#{i}/#{nversions}]\r"
161
+
162
+ # Store it so we can show it at the end
163
+ if version == 'HEAD'
164
+ head_data = data
122
165
  end
123
166
 
124
- sha = @repo.write(@data.to_json, :blob)
125
167
  output_index.add(:path => "#{version}.json", :oid => sha, :mode => 0100644)
168
+ examples.each do |path, id|
169
+ output_index.add(:path => path, :oid => id, :mode => 0100644)
170
+ end
171
+
172
+ if head_data
173
+ puts ''
174
+ show_warnings(data)
175
+ end
176
+
126
177
  end
127
178
 
128
179
  project = {
@@ -130,7 +181,6 @@ class Docurium
130
181
  :github => @options['github'],
131
182
  :name => @options['name'],
132
183
  :signatures => @sigs,
133
- :groups => @groups
134
184
  }
135
185
  sha = @repo.write(project.to_json, :blob)
136
186
  output_index.add(:path => "project.json", :oid => sha, :mode => 0100644)
@@ -140,7 +190,7 @@ class Docurium
140
190
  refname = "refs/heads/#{br}"
141
191
  tsha = output_index.write_tree(@repo)
142
192
  puts "\twrote tree #{tsha}"
143
- ref = Rugged::Reference.lookup(@repo, refname)
193
+ ref = @repo.references[refname]
144
194
  user = { :name => @repo.config['user.name'], :email => @repo.config['user.email'], :time => Time.now }
145
195
  options = {}
146
196
  options[:tree] = tsha
@@ -154,12 +204,12 @@ class Docurium
154
204
  puts "\tupdated #{br}"
155
205
  end
156
206
 
157
- def show_warnings
207
+ def show_warnings(data)
158
208
  out '* checking your api'
159
209
 
160
210
  # check for unmatched paramaters
161
211
  unmatched = []
162
- @data[:functions].each do |f, fdata|
212
+ data[:functions].each do |f, fdata|
163
213
  unmatched << f if fdata[:comments] =~ /@param/
164
214
  end
165
215
  if unmatched.size > 0
@@ -181,27 +231,35 @@ class Docurium
181
231
  end
182
232
 
183
233
  def get_versions
184
- tags = []
185
- @repo.tags.each { |tag| tags << tag.gsub(%r(^refs/tags/), '') }
186
- VersionSorter.sort(tags)
234
+ VersionSorter.sort(@repo.tags.map { |tag| tag.name.gsub(%r(^refs/tags/), '') })
187
235
  end
188
236
 
189
- def parse_headers(index)
190
- headers(index).each do |header|
191
- records = parse_header(index, header)
192
- update_globals(records)
237
+ def parse_headers(index, version)
238
+ headers = index.map { |e| e[:path] }.grep(/\.h$/)
239
+
240
+ files = headers.map do |file|
241
+ [file, @repo.lookup(index[file][:oid]).content]
193
242
  end
194
243
 
195
- @data[:groups] = group_functions
196
- @data[:types] = @data[:types].sort # make it an assoc array
197
- find_type_usage
244
+ data = init_data(version)
245
+ parser = DocParser.new
246
+ headers.each do |header|
247
+ records = parser.parse_file(header, files)
248
+ update_globals!(data, records)
249
+ end
250
+
251
+ data[:groups] = group_functions!(data)
252
+ data[:types] = data[:types].sort # make it an assoc array
253
+ find_type_usage!(data)
254
+
255
+ data
198
256
  end
199
257
 
200
258
  private
201
259
 
202
- def tally_sigs(version)
260
+ def tally_sigs!(version, data)
203
261
  @lastsigs ||= {}
204
- @data[:functions].each do |fun_name, fun_data|
262
+ data[:functions].each do |fun_name, fun_data|
205
263
  if !@sigs[fun_name]
206
264
  @sigs[fun_name] ||= {:exists => [], :changes => {}}
207
265
  else
@@ -217,10 +275,10 @@ class Docurium
217
275
  def find_subtree(version, path)
218
276
  tree = nil
219
277
  if version == 'HEAD'
220
- tree = @repo.lookup(@repo.head.target).tree
278
+ tree = @repo.head.target.tree
221
279
  else
222
- trg = @repo.lookup(Rugged::Reference.lookup(@repo, "refs/tags/#{version}").target)
223
- if(trg.class == Rugged::Tag)
280
+ trg = @repo.references["refs/tags/#{version}"].target
281
+ if(trg.kind_of? Rugged::Tag::Annotation)
224
282
  trg = trg.target
225
283
  end
226
284
 
@@ -249,9 +307,9 @@ class Docurium
249
307
  !!@options['branch']
250
308
  end
251
309
 
252
- def group_functions
310
+ def group_functions!(data)
253
311
  func = {}
254
- @data[:functions].each_pair do |key, value|
312
+ data[:functions].each_pair do |key, value|
255
313
  if @options['prefix']
256
314
  k = key.gsub(@options['prefix'], '')
257
315
  else
@@ -262,8 +320,7 @@ class Docurium
262
320
  if !rest
263
321
  group = value[:file].gsub('.h', '').gsub('/', '_')
264
322
  end
265
- @data[:functions][key][:group] = group
266
- @groups[key] = group
323
+ data[:functions][key][:group] = group
267
324
  func[group] ||= []
268
325
  func[group] << key
269
326
  func[group].sort!
@@ -272,45 +329,31 @@ class Docurium
272
329
  func.to_a.sort
273
330
  end
274
331
 
275
- def headers(index = nil)
276
- h = []
277
- index.each do |entry|
278
- next unless entry[:path].match(/\.h$/)
279
- h << entry[:path]
280
- end
281
- h
282
- end
283
-
284
- def find_type_usage
332
+ def find_type_usage!(data)
285
333
  # go through all the functions and see where types are used and returned
286
334
  # store them in the types data
287
- @data[:functions].each do |func, fdata|
288
- @data[:types].each_with_index do |tdata, i|
335
+ data[:functions].each do |func, fdata|
336
+ data[:types].each_with_index do |tdata, i|
289
337
  type, typeData = tdata
290
- @data[:types][i][1][:used] ||= {:returns => [], :needs => []}
338
+ data[:types][i][1][:used] ||= {:returns => [], :needs => []}
291
339
  if fdata[:return][:type].index(/#{type}[ ;\)\*]/)
292
- @data[:types][i][1][:used][:returns] << func
293
- @data[:types][i][1][:used][:returns].sort!
340
+ data[:types][i][1][:used][:returns] << func
341
+ data[:types][i][1][:used][:returns].sort!
294
342
  end
295
343
  if fdata[:argline].index(/#{type}[ ;\)\*]/)
296
- @data[:types][i][1][:used][:needs] << func
297
- @data[:types][i][1][:used][:needs].sort!
344
+ data[:types][i][1][:used][:needs] << func
345
+ data[:types][i][1][:used][:needs].sort!
298
346
  end
299
347
  end
300
348
  end
301
349
  end
302
350
 
303
- def parse_header(index, path)
304
- id = index[path][:oid]
305
- blob = @repo.lookup(id)
306
- parser = Docurium::CParser.new
307
- parser.parse_text(path, blob.content)
308
- end
351
+ def update_globals!(data, recs)
352
+ return if recs.empty?
309
353
 
310
- def update_globals(recs)
311
354
  wanted = {
312
355
  :functions => %W/type value file line lineto args argline sig return group description comments/.map(&:to_sym),
313
- :types => %W/type value file line lineto block tdef comments/.map(&:to_sym),
356
+ :types => %W/decl type value file line lineto block tdef description comments fields/.map(&:to_sym),
314
357
  :globals => %W/value file line comments/.map(&:to_sym),
315
358
  :meta => %W/brief defgroup ingroup comments/.map(&:to_sym),
316
359
  }
@@ -331,7 +374,7 @@ class Docurium
331
374
  # process this type of record
332
375
  case r[:type]
333
376
  when :function
334
- @data[:functions][r[:name]] ||= {}
377
+ data[:functions][r[:name]] ||= {}
335
378
  wanted[:functions].each do |k|
336
379
  next unless r.has_key? k
337
380
  conents = nil
@@ -340,18 +383,18 @@ class Docurium
340
383
  else
341
384
  contents = r[k]
342
385
  end
343
- @data[:functions][r[:name]][k] = contents
386
+ data[:functions][r[:name]][k] = contents
344
387
  end
345
388
  file_map[r[:file]][:functions] << r[:name]
346
389
 
347
390
  when :define, :macro
348
- @data[:globals][r[:decl]] ||= {}
391
+ data[:globals][r[:decl]] ||= {}
349
392
  wanted[:globals].each do |k|
350
393
  next unless r.has_key? k
351
394
  if k == :description || k == :comments
352
- @data[:globals][r[:decl]][k] = md.render r[k]
395
+ data[:globals][r[:decl]][k] = md.render r[k]
353
396
  else
354
- @data[:globals][r[:decl]][k] = r[k]
397
+ data[:globals][r[:decl]][k] = r[k]
355
398
  end
356
399
  end
357
400
 
@@ -364,46 +407,52 @@ class Docurium
364
407
  if !r[:name]
365
408
  # Explode unnamed enum into multiple global defines
366
409
  r[:decl].each do |n|
367
- @data[:globals][n] ||= {
410
+ data[:globals][n] ||= {
368
411
  :file => r[:file], :line => r[:line],
369
412
  :value => "", :comments => md.render(r[:comments]),
370
413
  }
371
414
  m = /#{Regexp.quote(n)}/.match(r[:body])
372
415
  if m
373
- @data[:globals][n][:line] += m.pre_match.scan("\n").length
416
+ data[:globals][n][:line] += m.pre_match.scan("\n").length
374
417
  if m.post_match =~ /\s*=\s*([^,\}]+)/
375
- @data[:globals][n][:value] = $1
418
+ data[:globals][n][:value] = $1
376
419
  end
377
420
  end
378
421
  end
379
422
  else # enum has name
380
- @data[:types][r[:name]] ||= {}
423
+ data[:types][r[:name]] ||= {}
381
424
  wanted[:types].each do |k|
382
425
  next unless r.has_key? k
383
426
  contents = r[k]
384
427
  if k == :comments
385
428
  contents = md.render r[k]
386
429
  elsif k == :block
387
- old_block = @data[:types][r[:name]][k]
430
+ old_block = data[:types][r[:name]][k]
388
431
  contents = old_block ? [old_block, r[k]].join("\n") : r[k]
432
+ elsif k == :fields
433
+ type = data[:types][r[:name]]
434
+ type[:fields] = []
435
+ r[:fields].each do |f|
436
+ f[:comments] = md.render(f[:comments])
437
+ end
389
438
  end
390
- @data[:types][r[:name]][k] = contents
439
+ data[:types][r[:name]][k] = contents
391
440
  end
392
441
  end
393
442
 
394
443
  when :struct, :fnptr
395
- @data[:types][r[:name]] ||= {}
444
+ data[:types][r[:name]] ||= {}
396
445
  r[:value] ||= r[:name]
397
446
  wanted[:types].each do |k|
398
447
  next unless r.has_key? k
399
448
  if k == :comments
400
- @data[:types][r[:name]][k] = md.render r[k]
449
+ data[:types][r[:name]][k] = md.render r[k]
401
450
  else
402
- @data[:types][r[:name]][k] = r[k]
451
+ data[:types][r[:name]][k] = r[k]
403
452
  end
404
453
  end
405
454
  if r[:type] == :fnptr
406
- @data[:types][r[:name]][:type] = "function pointer"
455
+ data[:types][r[:name]][:type] = "function pointer"
407
456
  end
408
457
 
409
458
  else
@@ -412,7 +461,7 @@ class Docurium
412
461
 
413
462
  end
414
463
 
415
- @data[:files] << file_map.values[0]
464
+ data[:files] << file_map.values[0]
416
465
  end
417
466
 
418
467
  def add_dir_to_index(index, prefix, dir)