#!/usr/bin/env ratch # scan for notes # # This task scans source code for developer notes and writes to # well organized files. # TODO Add ability to read header notes. # FIXME Should each label of note be written to it's own file? DEFAULT_NOTE_FILES = ['lib/**/*', 'ext/**/*'] DEFAULT_NOTE_LABELS = ['TODO', 'FIXME'] DEFAULT_NOTE_FORMAT = 'rdoc' DEFAULT_NOTE_OUTPUT = 'doc/note' # main :note do notes end # Notes tool can lookup and list TODO, FIXME and # other types of labeled comments from source code. def notes data = configuration['notes'] files = data['files'] labels = data['labels'] format = data['format'] outout = data['output'] files ||= DEFAULT_NOTE_FILES labels ||= DEFAULT_NOTE_LABELS format ||= DEFAULT_NOTE_FORMAT output ||= DEFAULT_NOTE_OUTPUT labels = labels.split(',') if String === labels labels = [labels].flatten.compact output.chomp!('/') records, counts = extract_notes( labels, files ) notes = format_notes( labels, records, format ) if records.empty? puts "No #{labels.join('/')} notes." end save_notes( output, notes, labels ) puts counts.collect{|l,n| "#{n} #{l}s"}.join(', ') puts "Notes saved in #{output} folder." end # Gather notes. def extract_notes( labels, files=nil ) files = files || info.scripts || info.note_files || DEFAULT_NOTE_FILES files = Dir.multiglob_with_default(DEFAULT_NOTE_FILES, *files) counts = Hash.new(0) records = [] files.each do |fname| next unless fname =~ /.*rb/ # TODO should this be done? File.open(fname) do |f| line_no, save, text = 0, nil, nil while line = f.gets line_no += 1 labels.each do |label| if line =~ /^\s*#\s*#{Regexp.escape(label)}[:]?\s*(.*?)$/ file = fname text = '' save = {'label'=>label,'file'=>file,'line'=>line_no,'note'=>text} records << save counts[label] += 1 end end if text if line =~ /^\s*[#]{0,1}\s*$/ or line !~ /^\s*#/ or line =~ /^\s*#[+][+]/ text.strip! text = nil #records << save else text << line.gsub(/^\s*#\s*/,'') end end end end end return records, counts end # Format notes. def format_notes( labels, records, format=nil ) #return "No #{labels.join('/')} notes." if records.empty? #return {} if records.empty? notes = {} labels.each do |label| recs = records.select{ |r| r['label'] == label } next if recs.empty? out = "\n= #{label}\n" last_file = nil recs.sort!{ |a,b| a['file'] <=> b['file'] } recs.each do |record| if last_file != record['file'] out << "\n" last_file = record['file'] out << "file://#{record['file']}\n" end out << "* #{record['note'].rstrip} (#{record['line']})\n" end notes[label] = out end return notes end # Save notes. def save_notes( dir, notes, labels ) # Remove empty note files. (labels - notes.keys).each do |label| file = File.join(dir,label) rm(file) if File.file?(file) end # Create note files. notes.each do |label, note| file = File.join(dir,label) file = apply_naming_policy(file, 'txt') if dryrun? puts "write #{file}" else File.open(file,'w') do |f| f << note end end end end # out = '' # # case format # when 'yaml' # out << records.to_yaml # when 'list' # records.each do |record| # out << "* #{record['note']}\n" # end # else #when 'rdoc' # labels.each do |label| # recs = records.select{ |r| r['label'] == label } # next if recs.empty? # out << "\n= #{label}\n" # last_file = nil # recs.sort!{ |a,b| a['file'] <=> b['file'] } # recs.each do |record| # if last_file != record['file'] # out << "\n" # last_file = record['file'] # out << "file://#{record['file']}\n" # end # out << "* #{record['note'].rstrip} (#{record['line']})\n" # end # end # out << "\n---\n" # out << counts.collect{|l,n| "#{n} #{l}s"}.join(' ') # out << "\n" # end # # List TODO notes. Same as notes --label=TODO. # # def todo( options={} ) # options = options.to_openhash # options.label = 'TODO' # notes(options) # end # # # List FIXME notes. Same as notes --label=FIXME. # # def fixme( options={} ) # options = options.to_openhash # options.label = 'FIXME' # notes(options) # end