require 'pathname' module DNote # = Developer Notes # # This class goes through you source files and compiles a list # of any labeled comments. Labels are single word prefixes to # a comment ending in a colon. # # By default the labels supported are TODO, FIXME, OPTIMIZE # and DEPRECATE. # #-- # TODO: Add ability to read header notes. They often # have a outline format, rather then the single line. #++ class Notes include Enumerable # Default paths (all ruby scripts). DEFAULT_PATHS = ["**/*.rb"] # Default note labels to look for in source code. DEFAULT_LABELS = ['TODO', 'FIXME', 'OPTIMIZE', 'DEPRECATE'] # Paths to search. attr_accessor :paths # Labels to document. Defaults are: TODO, FIXME, OPTIMIZE and DEPRECATE. attr_accessor :labels # def initialize(paths, labels=nil) @paths = [paths || DEFAULT_PATHS ].flatten @labels = [labels || DEFAULT_LABELS].flatten parse end # def notes @notes end # def counts @counts end # def each(&block) notes.each(&block) end # def empty? notes.empty? end # def labels=(labels) @labels = ( case labels when String labels.split(/[:;,]/) else labels = [labels].flatten.compact.uniq.map{ |s| s.to_s } end ) end # Gather and count notes. This returns two elements, # a hash in the form of label=>notes and a counts hash. def parse records, counts = [], Hash.new(0) files.each do |fname| next unless File.file?(fname) #next unless fname =~ /\.rb$/ # TODO: should this be done? File.open(fname) do |f| lineno, save, text = 0, nil, nil while line = f.gets lineno += 1 save = match_common(line, lineno, fname) || match_arbitrary(line, lineno, fname) if save #file = fname text = save['note'] #save = {'label'=>label,'file'=>file,'line'=>line_no,'note'=>text} records << save counts[save['label']] += 1 else if text if line =~ /^\s*[#]{0,1}\s*$/ or line !~ /^\s*#/ or line =~ /^\s*#[+][+]/ text.strip! text = nil else text << ' ' << line.gsub(/^\s*#\s*/,'') end end end end end end # organize the notes notes = organize(records) # @notes, @counts = notes, counts end # def files @files ||= ( [self.paths].flatten.map do |path| if File.directory?(path) Dir.glob(File.join(path, '**/*')) else Dir.glob(path) end end.flatten.uniq ) end # def match_common(line, lineno, file) rec = nil labels.each do |label| if md = /\#\s*#{Regexp.escape(label)}[:]?\s*(.*?)$/.match(line) text = md[1] rec = {'label'=>label,'file'=>file,'line'=>lineno,'note'=>text} end end return rec end # def match_arbitrary(line, lineno, file) rec = nil labels.each do |label| if md = /\#\s*([A-Z]+)[:]\s*(.*?)$/.match(line) label, text = md[1], md[2] rec = {'label'=>label,'file'=>file,'line'=>lineno,'note'=>text} end end return rec end # Organize records in heirarchical form. # def organize(records) orecs = {} records.each do |record| label = record['label'] file = record['file'] line = record['line'] note = record['note'].rstrip orecs[label] ||= {} orecs[label][file] ||= [] orecs[label][file] << [line, note] end orecs end # def to(format) __send__("to_#{format}") end # def to_yaml require 'yaml' notes.to_yaml end # def to_json begin require 'json' rescue LoadError require 'json_pure' end notes.to_json end # Soap envelope XML. def to_soap require 'soap/marshal' SOAP::Marshal.marshal(notes) end # XOXO microformat. def to_xoxo require 'xoxo' notes.to_xoxo end end end