lib/todidnt.rb in todidnt-0.2.0 vs lib/todidnt.rb in todidnt-0.3.1

- old
+ new

@@ -1,236 +1,56 @@ +require 'todidnt/cache' require 'todidnt/git_repo' require 'todidnt/git_command' require 'todidnt/todo_line' +require 'todidnt/git_history' require 'todidnt/html_generator' require 'chronic' require 'launchy' module Todidnt class CLI - VALID_COMMANDS = %w{all overdue history} + VALID_COMMANDS = %w{generate clear} def self.run(command, options) + command ||= 'generate' + if command && VALID_COMMANDS.include?(command) self.send(command, options) elsif command $stderr.puts("Sorry, `#{command}` is not a valid command.") exit - else - $stderr.puts("You must specify a command! Try `todidnt all`.") end end - def self.all(options) - all_lines = self.all_lines(options).sort_by(&:timestamp) - - puts "\nOpening results..." - - file_path = HTMLGenerator.generate(:all, :all_lines => all_lines) - Launchy.open("file://#{file_path}") - end - - def self.overdue(options) - date = Chronic.parse(options[:date] || 'now', :context => :past) - if date.nil? - $stderr.puts("Invalid date passed: #{options[:date]}") - exit - else - puts "Finding overdue TODOs (created before #{date.strftime('%F')})..." - end - - all_lines = self.all_lines(options) - - puts "\nResults:" - all_lines.sort_by do |line| - line.timestamp - end.select do |line| - line.timestamp < date.to_i - end.each do |line| - puts line.pretty - end - end - - def self.all_lines(options) + def self.generate(options) GitRepo.new(options[:path]).run do |path| - puts "Running in #{path || 'current directory'}..." + history = GitHistory.new + buckets, authors = history.timeline! + lines = TodoLine.all(["TODO"]) - puts "Found #{lines.count} TODOs. Blaming..." + lines.each do |todo| + blames = history.blames[todo.raw_content] - lines.each_with_index do |todo, i| - todo.populate_blame - $stdout.write "\rBlamed: #{i}/#{lines.count}" - end - - lines - end - end - - def self.history(options) - GitRepo.new(options[:path]).run do |path| - log = GitCommand.new(:log, [['-G', 'TODO'], ['--format="COMMIT %an %ae %at"'], ['-p'], ['-U0']]) - - history = [] - - blame_hash = {} - - puts "Going through log..." - patch_additions = [] - patch_deletions = [] - filename = nil - total = log.output_lines.count - - log.output_lines.reverse.each do |line| - if (summary = /^COMMIT (.*) (.*) (.*)/.match(line)) - name = summary[1] - email = summary[2] - time = summary[3] - - unless filename =~ TodoLine::IGNORE - # Put the additions in the blame hash so when someone removes we - # can tell who the original author was. Mrrrh, this isn't going to - # work if people add the same string (pretty common e.g. # TODO). - # We can figure this out later though. - patch_additions.each do |line| - blame_hash[line] ||= [] - blame_hash[line] << name - end - - deletions_by_author = {} - patch_deletions.each do |line| - author = blame_hash[line] && blame_hash[line].pop - - if author - deletions_by_author[author] ||= 0 - deletions_by_author[author] += 1 - else - puts "BAD BAD can't find original author: #{line}" - end - end - - history << { - :timestamp => time.to_i, - :author => name, - :additions => patch_additions.count, - :deletions => deletions_by_author[name] || 0 - } - - deletions_by_author.delete(name) - deletions_by_author.each do |author, deletion_count| - history << { - :timestamp => time.to_i, - :author => author, - :additions => 0, - :deletions => deletion_count - } - end - end - - patch_additions = [] - patch_deletions = [] - elsif (diff = /diff --git a\/(.*) b\/(.*)/.match(line)) - filename = diff[1] - elsif (diff = /^\+(.*TODO.*)/.match(line)) - patch_additions << diff[1] - elsif (diff = /^\-(.*TODO.*)/.match(line)) - patch_deletions << diff[1] + if blames && (metadata = blames.pop) + todo.author = metadata[:name] + todo.timestamp = metadata[:time] + else + todo.author = "(Not yet committed)" + todo.timestamp = Time.now.to_i end end - history.sort_by! {|slice| slice[:timestamp]} - min_commit_date = Time.at(history.first[:timestamp]) - max_commit_date = Time.at(history.last[:timestamp]) - - timespan = max_commit_date - min_commit_date - - # Figure out what the interval should be based on the total timespan. - if timespan > 86400 * 365 * 10 # 10+ years - interval = 86400 * 365 # years - elsif timespan > 86400 * 365 * 5 # 5-10 years - interval = 86400 * (365 / 2) # 6 months - elsif timespan > 86400 * 365 # 2-5 years - interval = 86400 * (365 / 4) # 3 months - elsif timespan > 86400 * 30 * 6 # 6 months-3 year - interval = 86400 * 30 # months - elsif timespan > 86400 * 1 # 1 month - 6 months - interval = 86400 * 7 - else # 0 - 2 months - interval = 86400 # days - end - - original_interval_start = Time.new(min_commit_date.year, min_commit_date.month, min_commit_date.day).to_i - interval_start = original_interval_start - interval_end = interval_start + interval - - puts "Finalizing timeline..." - buckets = [] - current_bucket_authors = {} - bucket_total = 0 - - i = 0 - # Going through the entire history of +/-'s of TODOs. - while i < history.length - should_increment = false - slice = history[i] - author = slice[:author] - - # Does the current slice exist inside the bucket we're currently - # in? If so, add it to the author's total and go to the next slice. - if slice[:timestamp] >= interval_start && slice[:timestamp] < interval_end - current_bucket_authors[author] ||= 0 - current_bucket_authors[author] += slice[:additions] - slice[:deletions] - bucket_total += slice[:additions] - slice[:deletions] - should_increment = true - end - - # If we're on the last slice, or the next slice would have been - # in a new bucket, finish the current bucket. - if i == (history.length - 1) || history[i + 1][:timestamp] >= interval_end - buckets << { - :timestamp => Time.at(interval_start).strftime('%D'), - :authors => current_bucket_authors, - :total => bucket_total - } - interval_start += interval - interval_end += interval - - current_bucket_authors = current_bucket_authors.clone - end - - i += 1 if should_increment - end - - authors = Set.new - contains_other = false - buckets.each do |bucket| - significant_authors = {} - other_count = 0 - bucket[:authors].each do |author, count| - # Only include the author if they account for more than > 3% of - # the TODOs in this bucket. - if count > bucket[:total] * 0.03 - significant_authors[author] = count - authors << author - else - other_count += count - end - end - - if other_count > 0 - significant_authors['Other'] = other_count - contains_other = true - end - - bucket[:authors] = significant_authors - end - - if contains_other - authors << 'Other' - end - + file_path = HTMLGenerator.generate(:all, :all_lines => lines.sort_by(&:timestamp).reverse) file_path = HTMLGenerator.generate(:history, :data => {:history => buckets.map {|h| h[:authors].merge('Date' => h[:timestamp]) }, :authors => authors.to_a}) Launchy.open("file://#{file_path}") end + end + + def self.clear(options) + puts "Deleting cache..." + Cache.clear! + puts "Done!" end end end