44require 'rocco'
55require 'docurium/version'
66require 'docurium/layout'
7+ require 'docurium/debug'
78require 'libdetect'
89require 'docurium/docparser'
910require 'pp'
2021class Docurium
2122 attr_accessor :branch , :output_dir , :data , :head_data
2223
23- def initialize ( config_file , repo = nil )
24+ def initialize ( config_file , cli_options = { } , repo = nil )
2425 raise "You need to specify a config file" if !config_file
2526 raise "You need to specify a valid config file" if !valid_config ( config_file )
2627 @sigs = { }
2728 @head_data = nil
2829 @repo = repo || Rugged ::Repository . discover ( config_file )
30+ @cli_options = cli_options
2931 end
3032
3133 def init_data ( version = 'HEAD' )
@@ -118,7 +120,7 @@ def generate_doc_for(version)
118120 index = Rugged ::Index . new
119121 read_subtree ( index , version , option_version ( version , 'input' , '' ) )
120122
121- data = parse_headers ( index , version , reference )
123+ data = parse_headers ( index , version )
122124 examples = format_examples! ( data , version )
123125 [ data , examples ]
124126 end
@@ -144,14 +146,14 @@ def process_project(versions)
144146 end
145147 end
146148
147- def generate_docs ( options )
149+ def generate_docs
148150 output_index = Rugged ::Index . new
149151 write_site ( output_index )
150152 @tf = File . expand_path ( File . join ( File . dirname ( __FILE__ ) , 'docurium' , 'layout.mustache' ) )
151153 versions = get_versions
152154 versions << 'HEAD'
153155 # If the user specified versions, validate them and overwrite
154- if !( vers = options [ :for ] ) . empty?
156+ if !( vers = ( @cli_options [ :for ] || [ ] ) ) . empty?
155157 vers . each do |v |
156158 next if versions . include? ( v )
157159 puts "Unknown version #{ v } "
@@ -161,13 +163,16 @@ def generate_docs(options)
161163 end
162164
163165 process_project ( versions ) do |i , version , result |
164- print "Writing documentation [#{ i } /#{ versions . count } ]\r "
165166 data , examples = result
166-
167167 sha = @repo . write ( data . to_json , :blob )
168- output_index . add ( :path => "#{ version } .json" , :oid => sha , :mode => 0100644 )
169- examples . each do |path , id |
170- output_index . add ( :path => path , :oid => id , :mode => 0100644 )
168+
169+ print "Generating documentation [#{ i } /#{ versions . count } ]\r "
170+
171+ unless dry_run?
172+ output_index . add ( :path => "#{ version } .json" , :oid => sha , :mode => 0100644 )
173+ examples . each do |path , id |
174+ output_index . add ( :path => path , :oid => id , :mode => 0100644 )
175+ end
171176 end
172177 end
173178
@@ -176,6 +181,8 @@ def generate_docs(options)
176181 show_warnings ( head_data )
177182 end
178183
184+ return if dry_run?
185+
179186 # We tally the signatures in the order they finished, which is
180187 # arbitrary due to the concurrency, so we need to sort them once
181188 # they've finished.
@@ -228,29 +235,122 @@ def force_utf8(data)
228235 end
229236 end
230237
231- def show_warnings ( data )
232- out '* checking your api'
238+ class Warning
239+ class UnmatchedParameter < Warning
240+ def initialize ( function , opts = { } )
241+ super :unmatched_param , :function , function , opts
242+ end
243+
244+ def _message ; "unmatched param" ; end
245+ end
246+
247+ class SignatureChanged < Warning
248+ def initialize ( function , opts = { } )
249+ super :signature_changed , :function , function , opts
250+ end
251+
252+ def _message ; "signature changed" ; end
253+ end
254+
255+ class MissingDocumentation < Warning
256+ def initialize ( type , identifier , opts = { } )
257+ super :missing_documentation , type , identifier , opts
258+ end
259+
260+ def _message
261+ [ "%s %s is missing documentation" , :type , :identifier ]
262+ end
263+ end
264+
265+ WARNINGS = [
266+ :unmatched_param ,
267+ :signature_changed ,
268+ :missing_documentation ,
269+ ]
270+
271+ attr_reader :warning , :type , :identifier , :file , :line , :column
272+
273+ def initialize ( warning , type , identifier , opts = { } )
274+ raise ArgumentError . new ( "invalid warning class" ) unless WARNINGS . include? ( warning )
275+ @warning = warning
276+ @type = type
277+ @identifier = identifier
278+ if type = opts . delete ( :type )
279+ @file = type [ :file ]
280+ if input_dir = opts . delete ( :input_dir )
281+ File . expand_path ( File . join ( input_dir , @file ) )
282+ end
283+ @file ||= "<missing>"
284+ @line = type [ :line ] || 1
285+ @column = type [ :column ] || 1
286+ end
287+ end
288+
289+ def message
290+ msg = self . _message
291+ msg . shift % msg . map { |a | self . send ( a ) . to_s } if msg . kind_of? ( Array )
292+ end
293+ end
294+
295+ def collect_warnings ( data )
296+ warnings = [ ]
297+ input_dir = File . join ( @project_dir , option_version ( "HEAD" , 'input' ) )
233298
234299 # check for unmatched paramaters
235- unmatched = [ ]
236300 data [ :functions ] . each do |f , fdata |
237- unmatched << f if fdata [ :comments ] =~ /@param/
238- end
239- if unmatched . size > 0
240- out ' - unmatched params in'
241- unmatched . sort . each { |p | out ( "\t " + p ) }
301+ warnings << Warning ::UnmatchedParameter . new ( f , type : fdata , input_dir : input_dir ) if fdata [ :comments ] =~ /@param/
242302 end
243303
244304 # check for changed signatures
245305 sigchanges = [ ]
246306 @sigs . each do |fun , sig_data |
247- if sig_data [ :changes ] [ 'HEAD' ]
248- sigchanges << fun
307+ warnings << Warning ::SignatureChanged . new ( fun ) if sig_data [ :changes ] [ 'HEAD' ]
308+ end
309+
310+ # check for undocumented things
311+ types = [ :functions , :callbacks , :globals , :types ]
312+ types . each do |type_id |
313+ under_type = type_id . tap { |t | break t . to_s [ 0 ..-2 ] . to_sym }
314+ data [ type_id ] . each do |ident , type |
315+ under_type = type [ :type ] if type_id == :types
316+
317+ warnings << Warning ::MissingDocumentation . new ( under_type , ident , type : type , input_dir : input_dir ) if type [ :description ] . empty?
318+
319+ case type [ :type ]
320+ when :struct
321+ if type [ :fields ]
322+ type [ :fields ] . each do |field |
323+ warnings << Warning ::MissingDocumentation . new ( :field , "#{ ident } .#{ field [ :name ] } " , type : type , input_dir : input_dir ) if field [ :comments ] . empty?
324+ end
325+ end
326+ end
249327 end
250328 end
251- if sigchanges . size > 0
252- out ' - signature changes in'
253- sigchanges . sort . each { |p | out ( "\t " + p ) }
329+ warnings
330+ end
331+
332+ def check_warnings ( options )
333+ versions = [ ]
334+ versions << get_versions . pop
335+ versions << 'HEAD'
336+
337+ process_project ( versions )
338+
339+ collect_warnings ( head_data ) . each do |warning |
340+ puts "#{ warning . file } :#{ warning . line } :#{ warning . column } : #{ warning . message } "
341+ end
342+ end
343+
344+ def show_warnings ( data )
345+ out '* checking your api'
346+
347+ collect_warnings ( data ) . group_by { |w | w . warning } . each do |klass , klass_warnings |
348+ klass_warnings . group_by { |w | w . type } . each do |type , type_warnings |
349+ out " - " + type_warnings [ 0 ] . message
350+ type_warnings . sort_by { |w | w . identifier } . each do |warning |
351+ out "\t " + warning . identifier
352+ end
353+ end
254354 end
255355 end
256356
@@ -269,10 +369,11 @@ def parse_headers(index, version)
269369 end
270370
271371 data = init_data ( version )
272- parser = DocParser . new
273- headers . each do |header |
274- records = parser . parse_file ( header , files )
275- update_globals! ( data , records )
372+ DocParser . with_files ( files , :prefix => version ) do |parser |
373+ headers . each do |header |
374+ records = parser . parse_file ( header , debug : interesting? ( :file , header ) )
375+ update_globals! ( data , records )
376+ end
276377 end
277378
278379 data [ :groups ] = group_functions! ( data )
@@ -345,16 +446,20 @@ def valid_config(file)
345446 def group_functions! ( data )
346447 func = { }
347448 data [ :functions ] . each_pair do |key , value |
449+ debug_set interesting? ( :function , key )
450+ debug "grouping #{ key } : #{ value } "
348451 if @options [ 'prefix' ]
349452 k = key . gsub ( @options [ 'prefix' ] , '' )
350453 else
351454 k = key
352455 end
353456 group , rest = k . split ( '_' , 2 )
457+ debug "grouped: k: #{ k } , group: #{ group } , rest: #{ rest } "
354458 if group . empty?
355459 puts "empty group for function #{ key } "
356460 next
357461 end
462+ debug "grouped: k: #{ k } , group: #{ group } , rest: #{ rest } "
358463 data [ :functions ] [ key ] [ :group ] = group
359464 func [ group ] ||= [ ]
360465 func [ group ] << key
@@ -400,6 +505,25 @@ def update_globals!(data, recs)
400505 md = Redcarpet ::Markdown . new ( Redcarpet ::Render ::HTML . new ( { } ) , :no_intra_emphasis => true )
401506 recs . each do |r |
402507
508+ types = %w( function file type ) . map ( &:to_sym )
509+ dbg = false
510+ types . each do |t |
511+ dbg ||= if r [ :type ] == t and interesting? ( t , r [ :name ] )
512+ true
513+ elsif t == :file and interesting? ( :file , r [ :file ] )
514+ true
515+ elsif [ :struct , :enum ] . include? ( r [ :type ] ) and interesting? ( :type , r [ :name ] )
516+ true
517+ else
518+ false
519+ end
520+ end
521+
522+ debug_set dbg
523+
524+ debug "processing record: #{ r } "
525+ debug
526+
403527 # initialize filemap for this file
404528 file_map [ r [ :file ] ] ||= {
405529 :file => r [ :file ] , :functions => [ ] , :meta => { } , :lines => 0
@@ -507,6 +631,10 @@ def update_globals!(data, recs)
507631 # Anything else we want to record?
508632 end
509633
634+ debug "processed record: #{ r } "
635+ debug
636+
637+ debug_restore
510638 end
511639
512640 data [ :files ] << file_map . values [ 0 ]
@@ -537,4 +665,12 @@ def write_site(index)
537665 def out ( text )
538666 puts text
539667 end
668+
669+ def dry_run?
670+ @cli_options [ :dry_run ]
671+ end
672+
673+ def interesting? ( type , what )
674+ @cli_options [ 'debug' ] || ( @cli_options [ "debug-#{ type } " ] || [ ] ) . include? ( what )
675+ end
540676end
0 commit comments