Skip to content

Commit 6e86255

Browse files
committed
Merge remote-tracking branch 'origin/master' into feature/cli-options
2 parents 3a4d7ea + a6f8fef commit 6e86255

File tree

3 files changed

+152
-41
lines changed

3 files changed

+152
-41
lines changed

bin/cm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ command :doc do |c|
2525
end
2626
end
2727

28+
desc 'Check documentation for warnings'
29+
long_desc 'Check a project\'s documentation for issues'
30+
command :check do |c|
31+
c.action do |global_options,options,args|
32+
file = args.first
33+
Docurium::CLI.check(file, options)
34+
end
35+
end
36+
2837
desc 'Generate Docurium config file template'
2938
long_desc 'Generate Docurium config file template'
3039
command :gen do |c|

lib/docurium.rb

Lines changed: 138 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
Rocco::Markdown = RedcarpetCompat
1919

2020
class Docurium
21-
attr_accessor :branch, :output_dir, :data
21+
attr_accessor :branch, :output_dir, :data, :head_data
2222

2323
def initialize(config_file, cli_options = {}, repo = nil)
2424
raise "You need to specify a config file" if !config_file
2525
raise "You need to specify a valid config file" if !valid_config(config_file)
2626
@sigs = {}
27+
@head_data = nil
2728
@repo = repo || Rugged::Repository.discover(config_file)
2829
@cli_options = cli_options
2930
end
@@ -121,22 +122,7 @@ def generate_doc_for(version)
121122
data
122123
end
123124

124-
def generate_docs
125-
output_index = Rugged::Index.new
126-
write_site(output_index)
127-
@tf = File.expand_path(File.join(File.dirname(__FILE__), 'docurium', 'layout.mustache'))
128-
versions = get_versions
129-
versions << 'HEAD'
130-
# If the user specified versions, validate them and overwrite
131-
if !(vers = @cli_options[:for]).empty?
132-
vers.each do |v|
133-
next if versions.include?(v)
134-
puts "Unknown version #{v}"
135-
exit(false)
136-
end
137-
versions = vers
138-
end
139-
125+
def process_project(versions)
140126
nversions = versions.size
141127
output = Queue.new
142128
pipes = {}
@@ -159,7 +145,6 @@ def generate_docs
159145
end
160146

161147
print "Generating documentation [0/#{nversions}]\r"
162-
head_data = nil
163148

164149
# This may seem odd, but we need to keep reading from the pipe or
165150
# the buffer will fill and they'll block and never exit. Therefore
@@ -180,27 +165,46 @@ def generate_docs
180165
# There's still some work we need to do serially
181166
tally_sigs!(version, data)
182167
force_utf8(data)
183-
sha = @repo.write(data.to_json, :blob) unless dry_run?
184-
185-
print "Generating documentation [#{i}/#{nversions}]\r"
186168

187169
# Store it so we can show it at the end
188-
if version == 'HEAD'
189-
head_data = data
170+
@head_data = data if version == 'HEAD'
171+
172+
yield i, nversions, version, data, examples if block_given?
173+
end
174+
end
175+
176+
def generate_docs(options)
177+
output_index = Rugged::Index.new
178+
write_site(output_index)
179+
@tf = File.expand_path(File.join(File.dirname(__FILE__), 'docurium', 'layout.mustache'))
180+
versions = get_versions
181+
versions << 'HEAD'
182+
# If the user specified versions, validate them and overwrite
183+
if !(vers = (options[:for] || [])).empty?
184+
vers.each do |v|
185+
next if versions.include?(v)
186+
puts "Unknown version #{v}"
187+
exit(false)
190188
end
189+
versions = vers
190+
end
191+
192+
process_project(versions) do |i, version, data, examples|
193+
@repo.write(data.to_json, :blob)
194+
195+
print "Generating documentation [#{i}/#{versions.count}]\r"
191196

192197
unless dry_run?
193198
output_index.add(:path => "#{version}.json", :oid => sha, :mode => 0100644)
194199
examples.each do |path, id|
195200
output_index.add(:path => path, :oid => id, :mode => 0100644)
196201
end
197202
end
203+
end
198204

199-
if head_data
200-
puts ''
201-
show_warnings(data)
202-
end
203-
205+
if head_data
206+
puts ''
207+
show_warnings(head_data)
204208
end
205209

206210
return if dry_run?
@@ -257,29 +261,122 @@ def force_utf8(data)
257261
end
258262
end
259263

260-
def show_warnings(data)
261-
out '* checking your api'
264+
class Warning
265+
class UnmatchedParameter < Warning
266+
def initialize(function, opts = {})
267+
super :unmatched_param, :function, function, opts
268+
end
269+
270+
def _message; "unmatched param"; end
271+
end
272+
273+
class SignatureChanged < Warning
274+
def initialize(function, opts = {})
275+
super :signature_changed, :function, function, opts
276+
end
277+
278+
def _message; "signature changed"; end
279+
end
280+
281+
class MissingDocumentation < Warning
282+
def initialize(type, identifier, opts = {})
283+
super :missing_documentation, type, identifier, opts
284+
end
285+
286+
def _message
287+
["%s %s is missing documentation", :type, :identifier]
288+
end
289+
end
290+
291+
WARNINGS = [
292+
:unmatched_param,
293+
:signature_changed,
294+
:missing_documentation,
295+
]
296+
297+
attr_reader :warning, :type, :identifier, :file, :line, :column
298+
299+
def initialize(warning, type, identifier, opts = {})
300+
raise ArgumentError.new("invalid warning class") unless WARNINGS.include?(warning)
301+
@warning = warning
302+
@type = type
303+
@identifier = identifier
304+
if type = opts.delete(:type)
305+
@file = type[:file]
306+
if input_dir = opts.delete(:input_dir)
307+
File.expand_path(File.join(input_dir, @file))
308+
end
309+
@file ||= "<missing>"
310+
@line = type[:line] || 1
311+
@column = type[:column] || 1
312+
end
313+
end
314+
315+
def message
316+
msg = self._message
317+
msg.shift % msg.map {|a| self.send(a).to_s } if msg.kind_of?(Array)
318+
end
319+
end
320+
321+
def collect_warnings(data)
322+
warnings = []
323+
input_dir = File.join(@project_dir, option_version("HEAD", 'input'))
262324

263325
# check for unmatched paramaters
264-
unmatched = []
265326
data[:functions].each do |f, fdata|
266-
unmatched << f if fdata[:comments] =~ /@param/
267-
end
268-
if unmatched.size > 0
269-
out ' - unmatched params in'
270-
unmatched.sort.each { |p| out ("\t" + p) }
327+
warnings << Warning::UnmatchedParameter.new(f, type: fdata, input_dir: input_dir) if fdata[:comments] =~ /@param/
271328
end
272329

273330
# check for changed signatures
274331
sigchanges = []
275332
@sigs.each do |fun, sig_data|
276-
if sig_data[:changes]['HEAD']
277-
sigchanges << fun
333+
warnings << Warning::SignatureChanged.new(fun) if sig_data[:changes]['HEAD']
334+
end
335+
336+
# check for undocumented things
337+
types = [:functions, :callbacks, :globals, :types]
338+
types.each do |type_id|
339+
under_type = type_id.tap {|t| break t.to_s[0..-2].to_sym }
340+
data[type_id].each do |ident, type|
341+
under_type = type[:type] if type_id == :types
342+
343+
warnings << Warning::MissingDocumentation.new(under_type, ident, type: type, input_dir: input_dir) if type[:description].empty?
344+
345+
case type[:type]
346+
when :struct
347+
if type[:fields]
348+
type[:fields].each do |field|
349+
warnings << Warning::MissingDocumentation.new(:field, "#{ident}.#{field[:name]}", type: type, input_dir: input_dir) if field[:comments].empty?
350+
end
351+
end
352+
end
278353
end
279354
end
280-
if sigchanges.size > 0
281-
out ' - signature changes in'
282-
sigchanges.sort.each { |p| out ("\t" + p) }
355+
warnings
356+
end
357+
358+
def check_warnings(options)
359+
versions = []
360+
versions << get_versions.pop
361+
versions << 'HEAD'
362+
363+
process_project(versions)
364+
365+
collect_warnings(head_data).each do |warning|
366+
puts "#{warning.file}:#{warning.line}:#{warning.column}: #{warning.message}"
367+
end
368+
end
369+
370+
def show_warnings(data)
371+
out '* checking your api'
372+
373+
collect_warnings(data).group_by {|w| w.warning }.each do |klass, klass_warnings|
374+
klass_warnings.group_by {|w| w.type }.each do |type, type_warnings|
375+
out " - " + type_warnings[0].message
376+
type_warnings.sort_by {|w| w.identifier }.each do |warning|
377+
out "\t" + warning.identifier
378+
end
379+
end
283380
end
284381
end
285382

lib/docurium/cli.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ def self.doc(idir, options)
66
doc.generate_docs
77
end
88

9+
def self.check(idir, options)
10+
doc = Docurium.new(idir)
11+
doc.check_warnings(options)
12+
end
13+
914
def self.gen(file)
1015

1116
temp = <<-TEMPLATE

0 commit comments

Comments
 (0)