docurium 0.3.2 → 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.
@@ -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)