docurium 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +5 -0
  3. data/LICENCE +19 -0
  4. data/README.md +39 -0
  5. data/TODO.txt +19 -0
  6. data/bin/cm +31 -0
  7. data/docurium.gemspec +25 -0
  8. data/lib/docurium.rb +435 -0
  9. data/lib/docurium/cli.rb +10 -0
  10. data/site/css/style.css +236 -0
  11. data/site/images/search_icon.png +0 -0
  12. data/site/index.html +65 -0
  13. data/site/js/backbone.js +27 -0
  14. data/site/js/docurium.js +649 -0
  15. data/site/js/json2.js +26 -0
  16. data/site/js/underscore.js +26 -0
  17. data/site/shared/css/documentation.css +797 -0
  18. data/site/shared/css/pygments.css +60 -0
  19. data/site/shared/images/active-arrow.png +0 -0
  20. data/site/shared/images/background-v2.png +0 -0
  21. data/site/shared/images/background-white.png +0 -0
  22. data/site/shared/images/dropdown_sprites.jpg +0 -0
  23. data/site/shared/images/footer_logo.png +0 -0
  24. data/site/shared/images/logo.png +0 -0
  25. data/site/shared/images/nav-rule.png +0 -0
  26. data/site/shared/images/next_step_arrow.gif +0 -0
  27. data/site/shared/images/qmark.png +0 -0
  28. data/site/shared/js/documentation.js +43 -0
  29. data/site/shared/js/jquery.js +154 -0
  30. data/test/fixtures/git2/api.docurium +6 -0
  31. data/test/fixtures/git2/blob.h +121 -0
  32. data/test/fixtures/git2/commit.h +302 -0
  33. data/test/fixtures/git2/common.h +98 -0
  34. data/test/fixtures/git2/errors.h +149 -0
  35. data/test/fixtures/git2/index.h +270 -0
  36. data/test/fixtures/git2/object.h +147 -0
  37. data/test/fixtures/git2/odb.h +302 -0
  38. data/test/fixtures/git2/odb_backend.h +107 -0
  39. data/test/fixtures/git2/oid.h +191 -0
  40. data/test/fixtures/git2/refs.h +325 -0
  41. data/test/fixtures/git2/repository.h +217 -0
  42. data/test/fixtures/git2/revwalk.h +187 -0
  43. data/test/fixtures/git2/signature.h +81 -0
  44. data/test/fixtures/git2/tag.h +297 -0
  45. data/test/fixtures/git2/thread-utils.h +71 -0
  46. data/test/fixtures/git2/tree.h +266 -0
  47. data/test/fixtures/git2/types.h +162 -0
  48. data/test/fixtures/git2/zlib.h +58 -0
  49. data/test/repo_test.rb +101 -0
  50. data/test/test_helper.rb +29 -0
  51. metadata +167 -0
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ docs/
2
+ site/*.json
3
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ # vim:ft=ruby
data/LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Scott Chacon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Docurium
2
+
3
+ Docurium is a lightweight Doxygen replacement. It generates static HTML from the header files of a C project. It is meant to be simple, easy to navigate and git-tag aware. It is only meant to document your public interface, so it only knows about your header files.
4
+
5
+ I built it to replace the Doxygen generated documentation for the libgit2 project, so I'm only currently testing it against that. So, it is only known to include support for features and keywords used in that project currently. If you have a C library project you try this on and something is amiss, please file an issue or better yet, a pull request.
6
+
7
+ # Usage
8
+
9
+ Run the `cm` binary and pass it your Docurium config file.
10
+
11
+ $ cm doc api.docurium
12
+ * generating header based docs
13
+ - processing limit.h
14
+ - processing recess.h
15
+ * output html in docs/
16
+
17
+ The Docurium config files looks like this:
18
+
19
+ {
20
+ "name": "libgit2",
21
+ "github": "libgit2/libgit2",
22
+ "input": "include/git2",
23
+ "prefix": "git_",
24
+ "output": "docs",
25
+ "legacy": {
26
+ "input": {"src/git": ["v0.1.0"],
27
+ "src/git2": ["v0.2.0", "v0.3.0"]}
28
+ }
29
+ }
30
+
31
+ # Contributing
32
+
33
+ If you want to fix or change something, please fork on GitHub, push your change to a branch named after your change and send me a pull request.
34
+
35
+ # License
36
+
37
+ MIT, see LICENCE file
38
+
39
+
data/TODO.txt ADDED
@@ -0,0 +1,19 @@
1
+ TODO
2
+
3
+ * show unmatched params when compiling HEAD (-w?)
4
+
5
+ * links to example usage from example docco'd code
6
+
7
+
8
+
9
+ * write directly to branch
10
+
11
+ * gem-ize
12
+
13
+ * add to libgit2 project
14
+
15
+
16
+ * only update certain tags / HEAD, force full update
17
+
18
+ * highlight menu item when selected
19
+
data/bin/cm ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'gli'
6
+ require 'docurium'
7
+ require 'docurium/cli'
8
+
9
+ include GLI
10
+
11
+ version Docurium::Version
12
+
13
+ desc 'Generate HTML documentation'
14
+ long_desc 'Generate HTML docs from a Docurium config file'
15
+ command :doc do |c|
16
+ c.action do |global_options,options,args|
17
+ file = args.first
18
+ Docurium::CLI.doc(file, options)
19
+ end
20
+ end
21
+
22
+ pre { |global,command,options,args| true }
23
+
24
+ post { |global,command,options,args| true }
25
+
26
+ on_error do |exception|
27
+ puts exception
28
+ puts exception.backtrace
29
+ end
30
+
31
+ exit GLI.run(ARGV)
data/docurium.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'docurium'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "docurium"
7
+ s.version = Docurium::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Scott Chacon"]
10
+ s.email = ["schacon@gmail.com"]
11
+ s.homepage = "http://github.com/schacon/docurium"
12
+ s.summary = "A simpler, prettier Doxygen replacement."
13
+ s.description = s.summary
14
+
15
+ s.rubyforge_project = 'docurium'
16
+
17
+ s.add_dependency "version_sorter", "~>1.1.0"
18
+ s.add_development_dependency "bundler", "~>1.0"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
25
+
data/lib/docurium.rb ADDED
@@ -0,0 +1,435 @@
1
+ require 'json'
2
+ require 'tempfile'
3
+ require 'version_sorter'
4
+ require 'pp'
5
+
6
+ class Docurium
7
+ Version = VERSION = '0.0.1'
8
+
9
+ attr_accessor :branch, :output_dir, :data
10
+
11
+ def initialize(config_file)
12
+ raise "You need to specify a config file" if !config_file
13
+ raise "You need to specify a valid config file" if !valid_config(config_file)
14
+ @sigs = {}
15
+ clear_data
16
+ end
17
+
18
+ def clear_data(version = 'HEAD')
19
+ @data = {:files => [], :functions => {}, :globals => {}, :types => {}, :prefix => ''}
20
+ @data[:prefix] = option_version(version, 'input', '')
21
+ end
22
+
23
+ def option_version(version, option, default)
24
+ if @options['legacy']
25
+ if valhash = @options['legacy'][option]
26
+ valhash.each do |value, versions|
27
+ return value if versions.include?(version)
28
+ end
29
+ end
30
+ end
31
+ opt = @options[option]
32
+ opt = default if !opt
33
+ opt
34
+ end
35
+
36
+ def generate_docs
37
+ puts "generating docs"
38
+ outdir = mkdir_temp
39
+ copy_site(outdir)
40
+ versions = get_versions
41
+ versions << 'HEAD'
42
+ versions.each do |version|
43
+ puts "generating docs for version #{version}"
44
+ workdir = mkdir_temp
45
+ Dir.chdir(workdir) do
46
+ clear_data(version)
47
+ checkout(version, workdir)
48
+ puts "parsing headers"
49
+ parse_headers
50
+ tally_sigs(version)
51
+ File.open(File.join(outdir, "#{version}.json"), 'w+') do |f|
52
+ f.write(@data.to_json)
53
+ end
54
+ end
55
+ end
56
+
57
+ Dir.chdir(outdir) do
58
+ project = {
59
+ :versions => versions.reverse,
60
+ :github => @options['github'],
61
+ :name => @options['name'],
62
+ :signatures => @sigs
63
+ }
64
+ File.open("project.json", 'w+') do |f|
65
+ f.write(project.to_json)
66
+ end
67
+ end
68
+
69
+ if @options['branch']
70
+ write_branch
71
+ else
72
+ final_dir = File.join(@project_dir, @options['output'] || 'docs')
73
+ FileUtils.mkdir_p(final_dir)
74
+ Dir.chdir(final_dir) do
75
+ FileUtils.cp_r(File.join(outdir, '.'), '.')
76
+ end
77
+ end
78
+ end
79
+
80
+ def get_versions
81
+ VersionSorter.sort(git('tag').split("\n"))
82
+ end
83
+
84
+ def parse_headers
85
+ headers.each do |header|
86
+ parse_header(header)
87
+ end
88
+ @data[:groups] = group_functions
89
+ @data[:types] = @data[:types].sort # make it an assoc array
90
+ find_type_usage
91
+ end
92
+
93
+ private
94
+
95
+ def tally_sigs(version)
96
+ @lastsigs ||= {}
97
+ @data[:functions].each do |fun_name, fun_data|
98
+ if !@sigs[fun_name]
99
+ @sigs[fun_name] ||= {:exists => [], :changes => {}}
100
+ else
101
+ if @lastsigs[fun_name] != fun_data[:sig]
102
+ @sigs[fun_name][:changes][version] = true
103
+ end
104
+ end
105
+ @sigs[fun_name][:exists] << version
106
+ @lastsigs[fun_name] = fun_data[:sig]
107
+ end
108
+ end
109
+
110
+ def git(command)
111
+ out = ''
112
+ Dir.chdir(@project_dir) do
113
+ out = `git #{command}`
114
+ end
115
+ out.strip
116
+ end
117
+
118
+ def checkout(version, workdir)
119
+ ENV['GIT_INDEX_FILE'] = mkfile_temp
120
+ ENV['GIT_WORK_TREE'] = workdir
121
+ ENV['GIT_DIR'] = File.join(@project_dir, '.git')
122
+ `git read-tree #{version}:#{@data[:prefix]}`
123
+ `git checkout-index -a`
124
+ ENV.delete('GIT_INDEX_FILE')
125
+ ENV.delete('GIT_WORK_TREE')
126
+ ENV.delete('GIT_DIR')
127
+ end
128
+
129
+ def valid_config(file)
130
+ return false if !File.file?(file)
131
+ fpath = File.expand_path(file)
132
+ @project_dir = File.dirname(fpath)
133
+ @config_file = File.basename(fpath)
134
+ @options = JSON.parse(File.read(fpath))
135
+ true
136
+ end
137
+
138
+ def group_functions
139
+ func = {}
140
+ @data[:functions].each_pair do |key, value|
141
+ if @options['prefix']
142
+ k = key.gsub(@options['prefix'], '')
143
+ else
144
+ k = key
145
+ end
146
+ group, rest = k.split('_', 2)
147
+ next if group.empty?
148
+ if !rest
149
+ group = value[:file].gsub('.h', '').gsub('/', '_')
150
+ end
151
+ func[group] ||= []
152
+ func[group] << key
153
+ func[group].sort!
154
+ end
155
+ misc = []
156
+ func.to_a.sort
157
+ end
158
+
159
+ def headers
160
+ h = []
161
+ Dir.glob(File.join('**/*.h')).each do |header|
162
+ next if !File.file?(header)
163
+ h << header
164
+ end
165
+ h
166
+ end
167
+
168
+ def find_type_usage
169
+ # go through all the functions and see where types are used and returned
170
+ # store them in the types data
171
+ @data[:functions].each do |func, fdata|
172
+ @data[:types].each_with_index do |tdata, i|
173
+ type, typeData = tdata
174
+ @data[:types][i][1][:used] ||= {:returns => [], :needs => []}
175
+ if fdata[:return][:type].index(/#{type}[ ;\)\*]/)
176
+ @data[:types][i][1][:used][:returns] << func
177
+ @data[:types][i][1][:used][:returns].sort!
178
+ end
179
+ if fdata[:argline].index(/#{type}[ ;\)\*]/)
180
+ @data[:types][i][1][:used][:needs] << func
181
+ @data[:types][i][1][:used][:needs].sort!
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def header_content(header_path)
188
+ File.readlines(header_path)
189
+ end
190
+
191
+ def parse_header(filepath)
192
+ lineno = 0
193
+ content = header_content(filepath)
194
+
195
+ # look for structs and enums
196
+ in_block = false
197
+ block = ''
198
+ linestart = 0
199
+ tdef, type, name = nil
200
+ content.each do |line|
201
+ lineno += 1
202
+ line = line.strip
203
+
204
+ if line[0, 1] == '#' #preprocessor
205
+ if m = /\#define (.*?) (.*)/.match(line)
206
+ @data[:globals][m[1]] = {:value => m[2].strip, :file => filepath, :line => lineno}
207
+ else
208
+ next
209
+ end
210
+ end
211
+
212
+ if m = /^(typedef )*(struct|enum) (.*?)(\{|(\w*?);)/.match(line)
213
+ tdef = m[1] # typdef or nil
214
+ type = m[2] # struct or enum
215
+ name = m[3] # name or nil
216
+ linestart = lineno
217
+ name.strip! if name
218
+ tdef.strip! if tdef
219
+ if m[4] == '{'
220
+ # struct or enum
221
+ in_block = true
222
+ else
223
+ # single line, probably typedef
224
+ val = m[4].gsub(';', '').strip
225
+ if !name.empty?
226
+ name = name.gsub('*', '').strip
227
+ @data[:types][name] = {:tdef => tdef, :type => type, :value => val, :file => filepath, :line => lineno}
228
+ end
229
+ end
230
+ elsif m = /\}(.*?);/.match(line)
231
+ if !m[1].strip.empty?
232
+ name = m[1].strip
233
+ end
234
+ name = name.gsub('*', '').strip
235
+ @data[:types][name] = {:block => block, :tdef => tdef, :type => type, :value => val, :file => filepath, :line => linestart, :lineto => lineno}
236
+ in_block = false
237
+ block = ''
238
+ elsif in_block
239
+ block += line + "\n"
240
+ end
241
+ end
242
+
243
+ in_comment = false
244
+ in_block = false
245
+ current = -1
246
+ data = []
247
+ lineno = 0
248
+ # look for functions
249
+ content.each do |line|
250
+ lineno += 1
251
+ line = line.strip
252
+ next if line.size == 0
253
+ next if line[0, 1] == '#'
254
+ in_block = true if line =~ /\{/
255
+ if m = /(.*?)\/\*(.*?)\*\//.match(line)
256
+ code = m[1]
257
+ comment = m[2]
258
+ current += 1
259
+ data[current] ||= {:comments => clean_comment(comment), :code => [code], :line => lineno}
260
+ elsif m = /(.*?)\/\/(.*?)/.match(line)
261
+ code = m[1]
262
+ comment = m[2]
263
+ current += 1
264
+ data[current] ||= {:comments => clean_comment(comment), :code => [code], :line => lineno}
265
+ else
266
+ if line =~ /\/\*/
267
+ in_comment = true
268
+ current += 1
269
+ end
270
+ data[current] ||= {:comments => '', :code => [], :line => lineno}
271
+ data[current][:lineto] = lineno
272
+ if in_comment
273
+ data[current][:comments] += clean_comment(line) + "\n"
274
+ else
275
+ data[current][:code] << line
276
+ end
277
+ if (m = /(.*?);$/.match(line)) && (data[current][:code].size > 0) && !in_block
278
+ current += 1
279
+ end
280
+ in_comment = false if line =~ /\*\//
281
+ in_block = false if line =~ /\}/
282
+ end
283
+ end
284
+ data.compact!
285
+ meta = extract_meta(data)
286
+ funcs = extract_functions(filepath, data)
287
+ @data[:files] << {:file => filepath, :meta => meta, :functions => funcs, :lines => lineno}
288
+ end
289
+
290
+ def clean_comment(comment)
291
+ comment = comment.gsub(/^\/\//, '')
292
+ comment = comment.gsub(/^\/\**/, '')
293
+ comment = comment.gsub(/^\**/, '')
294
+ comment = comment.gsub(/^[\w\*]*\//, '')
295
+ comment
296
+ end
297
+
298
+ # go through all the comment blocks and extract:
299
+ # @file, @brief, @defgroup and @ingroup
300
+ def extract_meta(data)
301
+ file, brief, defgroup, ingroup = nil
302
+ data.each do |block|
303
+ block[:comments].each do |comment|
304
+ m = []
305
+ file = m[1] if m = /@file (.*?)$/.match(comment)
306
+ brief = m[1] if m = /@brief (.*?)$/.match(comment)
307
+ defgroup = m[1] if m = /@defgroup (.*?)$/.match(comment)
308
+ ingroup = m[1] if m = /@ingroup (.*?)$/.match(comment)
309
+ end
310
+ end
311
+ {:file => file, :brief => brief, :defgroup => defgroup, :ingroup => ingroup}
312
+ end
313
+
314
+ def extract_functions(file, data)
315
+ @data[:functions]
316
+ funcs = []
317
+ data.each do |block|
318
+ ignore = false
319
+ code = block[:code].join(" ")
320
+ code = code.gsub(/\{(.*)\}/, '') # strip inline code
321
+ rawComments = block[:comments]
322
+ comments = block[:comments]
323
+
324
+ if m = /^(.*?) ([a-z_]+)\((.*)\)/.match(code)
325
+ ret = m[1].strip
326
+ if r = /\((.*)\)/.match(ret) # strip macro
327
+ ret = r[1]
328
+ end
329
+ fun = m[2].strip
330
+ origArgs = m[3].strip
331
+
332
+ # replace ridiculous syntax
333
+ args = origArgs.gsub(/(\w+) \(\*(.*?)\)\(([^\)]*)\)/) do |m|
334
+ type, name = $1, $2
335
+ cast = $3.gsub(',', '###')
336
+ "#{type}(*)(#{cast}) #{name}"
337
+ end
338
+
339
+ args = args.split(',').map do |arg|
340
+ argarry = arg.split(' ')
341
+ var = argarry.pop
342
+ type = argarry.join(' ').gsub('###', ',') + ' '
343
+
344
+ ## split pointers off end of type or beg of name
345
+ var.gsub!('*') do |m|
346
+ type += '*'
347
+ ''
348
+ end
349
+ desc = ''
350
+ comments = comments.gsub(/\@param #{Regexp.escape(var)} ([^@]*)/m) do |m|
351
+ desc = $1.gsub("\n", ' ').gsub("\t", ' ').strip
352
+ ''
353
+ end
354
+ ## TODO: parse comments to extract data about args
355
+ {:type => type.strip, :name => var, :comment => desc}
356
+ end
357
+
358
+ sig = args.map do |arg|
359
+ arg[:type].to_s
360
+ end.join('::')
361
+
362
+ return_comment = ''
363
+ comments.gsub!(/\@return ([^@]*)/m) do |m|
364
+ return_comment = $1.gsub("\n", ' ').gsub("\t", ' ').strip
365
+ ''
366
+ end
367
+
368
+ comments = strip_block(comments)
369
+ comment_lines = comments.split("\n\n")
370
+
371
+ desc = ''
372
+ if comments.size > 0
373
+ desc = comment_lines.shift.split("\n").map { |e| e.strip }.join(' ')
374
+ comments = comment_lines.join("\n\n").strip
375
+ end
376
+
377
+ next if fun == 'defined'
378
+ @data[:functions][fun] = {
379
+ :description => desc,
380
+ :return => {:type => ret, :comment => return_comment},
381
+ :args => args,
382
+ :argline => origArgs,
383
+ :file => file,
384
+ :line => block[:line],
385
+ :lineto => block[:lineto],
386
+ :comments => comments,
387
+ :sig => sig,
388
+ :rawComments => rawComments
389
+ }
390
+ funcs << fun
391
+ end
392
+ end
393
+ funcs
394
+ end
395
+
396
+ # TODO: rolled this back, want to strip the first few spaces, not everything
397
+ def strip_block(block)
398
+ block.strip
399
+ end
400
+
401
+ def write_branch
402
+ puts "Writing to branch #{@branch}"
403
+ puts "Done!"
404
+ end
405
+
406
+ def mkdir_temp
407
+ tf = Tempfile.new('docurium')
408
+ tpath = tf.path
409
+ tf.unlink
410
+ FileUtils.mkdir_p(tpath)
411
+ tpath
412
+ end
413
+
414
+ def mkfile_temp
415
+ tf = Tempfile.new('docurium-index')
416
+ tpath = tf.path
417
+ tf.close
418
+ tpath
419
+ end
420
+
421
+
422
+ def copy_site(outdir)
423
+ puts "Copying site files to temp path (#{outdir})"
424
+ here = File.expand_path(File.dirname(__FILE__))
425
+ FileUtils.mkdir_p(outdir)
426
+ Dir.chdir(outdir) do
427
+ FileUtils.cp_r(File.join(here, '..', 'site', '.'), '.')
428
+ end
429
+ end
430
+
431
+ def write_dir
432
+ puts "Writing to directory #{output_dir}"
433
+ puts "Done!"
434
+ end
435
+ end