From c4536b8cc5b8807ede0650172eb460e33b529f65 Mon Sep 17 00:00:00 2001 From: "@@@No user configured@@@" <@@@No user configured@@@> Date: Wed, 24 Sep 2014 09:28:17 +0200 Subject: [PATCH] Re-organize in subdirectories. --- Gemfile | 3 + Gemfile.lock | 8 + bin/git-timecost | 344 +------------------------------------- lib/timecost.rb | 6 + lib/timecost/cli.rb | 179 ++++++++++++++++++++ lib/timecost/commit.rb | 14 ++ lib/timecost/range.rb | 107 ++++++++++++ lib/timecost/rangelist.rb | 46 +++++ 8 files changed, 367 insertions(+), 340 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 lib/timecost.rb create mode 100644 lib/timecost/cli.rb create mode 100644 lib/timecost/commit.rb create mode 100644 lib/timecost/range.rb create mode 100644 lib/timecost/rangelist.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..912a1f1 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +# A sample Gemfile +source "https://rubygems.org" + diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..fe80d20 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,8 @@ +GEM + remote: https://rubygems.org/ + specs: + +PLATFORMS + ruby + +DEPENDENCIES diff --git a/bin/git-timecost b/bin/git-timecost index 12c879e..4acc0b2 100755 --- a/bin/git-timecost +++ b/bin/git-timecost @@ -6,350 +6,14 @@ require 'date' require 'optparse' require 'yaml' -class GitExtractor +$:.insert 0, 'lib' +require 'timecost' - class Commit - attr_accessor :author, :commit, :date, :note - def initialize commit - @commit = commit - @note = nil - @author = nil - @date = nil - end - - end - - class Range - attr_accessor :time_start, :time_stop, :commits - - def initialize config, commit - @config = config - @time_stop = DateTime.parse(commit.date) - @time_start = @time_stop - (@config[:range_granularity] * 3 / 24.0) - @commits = [commit] - self - end - - def merge range - # B -----[----]---- - # A --[----]------ - # = ---[------]---- - - # minimum of both - new_start = if range.time_start < @time_start then range.time_start - else @time_start - end - - new_end = if range.time_stop >= @time_stop then range.time_stop - else @time_stop - end - - @time_start = new_start - @time_stop = new_end - @commits.concat range.commits - end - - def overlap? range - result = false - - # Ref ----[----]----- - # overlapping : - # A -[----]-------- - # B -------[----]-- - # C -[----------]-- - # D ------[]------- - # non-overlapping : - # E -[]------------ - # F -----------[]-- - - start_before_start = (range.time_start < @time_start) - start_after_start = (range.time_start >= @time_start) - start_after_stop = (range.time_start >= @time_stop) - start_before_stop = (range.time_start < @time_stop) - - stop_before_stop = (range.time_stop < @time_stop) - stop_after_stop = (range.time_stop >= @time_stop) - stop_before_start = (range.time_stop < @time_start) - stop_after_start = (range.time_stop >= @time_start) - - - # A case - if start_before_start and start_before_stop and - stop_after_start and stop_before_stop then - result = true - end - - # B case - if start_after_start and start_before_stop and - stop_after_start and stop_after_stop then - result = true - end - - # C case - if start_before_start and start_before_stop and - stop_after_start and stop_after_stop then - result = true - end - - # D case - if start_after_start and start_before_stop and - stop_after_start and stop_before_stop then - result = true - end - - return result - end - - def fixed_start - return @time_start + (@config[:range_granularity]/24.0) - end - - def diff - return ("%.2f" % ((@time_stop - fixed_start).to_f * 24)).to_f - end - - def to_s - val = "(%s) %s - %s\n" % [diff, fixed_start, @time_stop] - @commits.each do |commit| - lines = [] - unless @config[:author_filter_enable] then - lines.push commit.author - end - lines.concat commit.note.split(/\n/) - r = lines.map{ |s| "\t %s" % s }.join "\n" - r[1] = '*' - val += r + "\n" - end - return val - end - end - - class RangeList - def initialize - @ranges = [] - end - - def add range - merged = false - merged_range = nil - - # merge - @ranges.each do |old| - #pp old - if old.overlap? range then - old.merge range - merged_range = old - merged = true - break - end - end - - # add if needed - if merged then - @ranges.delete merged_range - self.add merged_range - else - @ranges.push range - end - end - - def each - @ranges.each do |r| - yield r - end - end - - def sum - result = 0 - @ranges.each do |r| - result += r.diff - end - return result - end - end - - - def initialize - # FIXME: accept multiple authors - @config = { - :author_filter_enable => false, - :author_filter => ".*?", - - :date_filter_enable => false, - :date_filter => ".*?", - - :input_dump => [], - :output_dump => nil, - - :range_granularity => 0.5, # in decimal hours - - :verbose => false - } - @rangelist = nil - end - - def parse_cmdline args - opts = OptionParser.new do |opts| - opts.banner = "Usage: #{File.basename $0} [options]" - - opts.on_tail("-v","--verbose", "Run verbosely") do |v| - @config[:verbose] = true - end - - opts.on_tail("-h","--help", "Show this help") do - puts opts - exit 0 - end - - opts.on("-i","--input FILE", "Set input dump file") do |file| - @config[:input_dump] << file - end - - opts.on("-o","--output FILE", "Set output dump file") do |file| - @config[:output_dump] = file - end - - opts.on("-d","--date DATE", "Keep only commits since DATE") do |date| - puts "set date filter to #{date}" - @config[:date_filter] = DateTime.parse(date); - @config[:date_filter_enable] = true - end - - opts.on("-t","--time TIME", "Keep only commits on last TIME datys") do |time| - puts "set time filter to latest #{time} days" - @config[:date_filter] = DateTime.now - time.to_f; - puts "set date filter to date = #{@config[:date_filter]}" - @config[:date_filter_enable] = true - end - - opts.on("-a","--author AUTHOR", "Keep only commits by AUTHOR") do |author| - puts "set author filter to #{author}" - @config[:author_filter] = author - @config[:author_filter_enable] = true - end - - # overlap : - # - opts.on("-s","--scotch GRANULARITY", "Use GRANULARITY (decimal hours) to merge ranges") do |granularity| - puts "set scotch to #{granularity}" - @config[:range_granularity] = granularity.to_f - end - end - opts.parse! args - - end - - - def analyze_git - # git log - # foreach, create time range (before) + logs - process = IO.popen ["git", "log", - "--date=iso", - "--no-patch", - "--","."] - - - @rangelist = RangeList.new - commit = nil - loop do - line = process.gets - break if line.nil? - # utf-8 fix ? - # line.encode!( line.encoding, "binary", :invalid => :replace, :undef => :replace) - line.strip! - - case line - when /^commit (.*)$/ then - id = $1 - # merge ranges & push - unless commit.nil? then - range = Range.new @config, commit - @rangelist.add range - end - commit = Commit.new id - # puts "commit #{id}" - - when /^Author:\s*(.*?)\s*$/ then - unless commit.nil? then - commit.author = $1 - - if @config[:author_filter_enable] and - (not commit.author =~ /#{@config[:author_filter]}/) then - commit = nil - # reject - end - end - - when /^Date:\s*(.*?)\s*$/ then - unless commit.nil? then - commit.date = $1 - - if @config[:date_filter_enable] and - (DateTime.parse(commit.date) < @config[:date_filter]) then - commit = nil - # reject - end - end - - when /^\s*$/ then - # skip - - else - # add as note - unless commit.nil? then - commit.note = if commit.note.nil? then line - else commit.note + "\n" + line - end - end - end - - end - - end - - def analyze_dumps - #read ranges - @rangelist = RangeList.new - - @config[:input_dump].each do |filename| - rangelist = YAML::load(File.open(filename,"r")) - rangelist.each do |range| - @rangelist.add range - end - end - end - - def analyze - if @config[:input_dump].empty? then - analyze_git - else - analyze_dumps - end - end - - def export - return if @config[:output_dump].nil? - puts "Exporting to %s" % @config[:output_dump] - File.open(@config[:output_dump], "w") do |file| - file.puts YAML::dump(@rangelist) - end - end - - def report - return if not @config[:output_dump].nil? - - @rangelist.each do |r| - puts r.to_s + "\n" - end - puts "TOTAL: %.2f hours" % @rangelist.sum - end -end - - -app = GitExtractor.new +app = TimeCost::CLI.new app.parse_cmdline ARGV app.analyze app.export app.report + exit 0 diff --git a/lib/timecost.rb b/lib/timecost.rb new file mode 100644 index 0000000..107d9fb --- /dev/null +++ b/lib/timecost.rb @@ -0,0 +1,6 @@ + +require 'timecost/commit' +require 'timecost/range' +require 'timecost/rangelist' +require 'timecost/cli' + diff --git a/lib/timecost/cli.rb b/lib/timecost/cli.rb new file mode 100644 index 0000000..b40d089 --- /dev/null +++ b/lib/timecost/cli.rb @@ -0,0 +1,179 @@ +module TimeCost + class CLI + def initialize + # FIXME: accept multiple authors + @config = { + :author_filter_enable => false, + :author_filter => ".*?", + + :date_filter_enable => false, + :date_filter => ".*?", + + :input_dump => [], + :output_dump => nil, + + :range_granularity => 0.5, # in decimal hours + + :verbose => false + } + @rangelist = nil + end + + def parse_cmdline args + opts = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename $0} [options]" + + opts.on_tail("-v","--verbose", "Run verbosely") do |v| + @config[:verbose] = true + end + + opts.on_tail("-h","--help", "Show this help") do + puts opts + exit 0 + end + + opts.on("-i","--input FILE", "Set input dump file") do |file| + @config[:input_dump] << file + end + + opts.on("-o","--output FILE", "Set output dump file") do |file| + @config[:output_dump] = file + end + + opts.on("-d","--date DATE", "Keep only commits since DATE") do |date| + puts "set date filter to #{date}" + @config[:date_filter] = DateTime.parse(date); + @config[:date_filter_enable] = true + end + + opts.on("-t","--time TIME", "Keep only commits on last TIME datys") do |time| + puts "set time filter to latest #{time} days" + @config[:date_filter] = DateTime.now - time.to_f; + puts "set date filter to date = #{@config[:date_filter]}" + @config[:date_filter_enable] = true + end + + opts.on("-a","--author AUTHOR", "Keep only commits by AUTHOR") do |author| + puts "set author filter to #{author}" + @config[:author_filter] = author + @config[:author_filter_enable] = true + end + + # overlap : + # + opts.on("-s","--scotch GRANULARITY", "Use GRANULARITY (decimal hours) to merge ranges") do |granularity| + puts "set scotch to #{granularity}" + @config[:range_granularity] = granularity.to_f + end + end + opts.parse! args + + end + + + def analyze_git + # git log + # foreach, create time range (before) + logs + process = IO.popen ["git", "log", + "--date=iso", + "--no-patch", + "--","."] + + + @rangelist = RangeList.new + commit = nil + loop do + line = process.gets + break if line.nil? + # utf-8 fix ? + # line.encode!( line.encoding, "binary", :invalid => :replace, :undef => :replace) + line.strip! + + case line + when /^commit (.*)$/ then + id = $1 + # merge ranges & push + unless commit.nil? then + range = Range.new @config, commit + @rangelist.add range + end + commit = Commit.new id + # puts "commit #{id}" + + when /^Author:\s*(.*?)\s*$/ then + unless commit.nil? then + commit.author = $1 + + if @config[:author_filter_enable] and + (not commit.author =~ /#{@config[:author_filter]}/) then + commit = nil + # reject + end + end + + when /^Date:\s*(.*?)\s*$/ then + unless commit.nil? then + commit.date = $1 + + if @config[:date_filter_enable] and + (DateTime.parse(commit.date) < @config[:date_filter]) then + commit = nil + # reject + end + end + + when /^\s*$/ then + # skip + + else + # add as note + unless commit.nil? then + commit.note = if commit.note.nil? then line + else commit.note + "\n" + line + end + end + end + + end + + end + + def analyze_dumps + #read ranges + @rangelist = RangeList.new + + @config[:input_dump].each do |filename| + rangelist = YAML::load(File.open(filename,"r")) + rangelist.each do |range| + @rangelist.add range + end + end + end + + def analyze + if @config[:input_dump].empty? then + analyze_git + else + analyze_dumps + end + end + + def export + return if @config[:output_dump].nil? + puts "Exporting to %s" % @config[:output_dump] + File.open(@config[:output_dump], "w") do |file| + file.puts YAML::dump(@rangelist) + end + end + + def report + return if not @config[:output_dump].nil? + + @rangelist.each do |r| + puts r.to_s + "\n" + end + puts "TOTAL: %.2f hours" % @rangelist.sum + end + end +end + diff --git a/lib/timecost/commit.rb b/lib/timecost/commit.rb new file mode 100644 index 0000000..27c2370 --- /dev/null +++ b/lib/timecost/commit.rb @@ -0,0 +1,14 @@ + +module TimeCost + + class Commit + attr_accessor :author, :commit, :date, :note + def initialize commit + @commit = commit + @note = nil + @author = nil + @date = nil + end + + end +end diff --git a/lib/timecost/range.rb b/lib/timecost/range.rb new file mode 100644 index 0000000..212ba50 --- /dev/null +++ b/lib/timecost/range.rb @@ -0,0 +1,107 @@ + +module TimeCost + class Range + attr_accessor :time_start, :time_stop, :commits + + def initialize config, commit + @config = config + @time_stop = DateTime.parse(commit.date) + @time_start = @time_stop - (@config[:range_granularity] * 3 / 24.0) + @commits = [commit] + self + end + + def merge range + # B -----[----]---- + # A --[----]------ + # = ---[------]---- + + # minimum of both + new_start = if range.time_start < @time_start then range.time_start + else @time_start + end + + new_end = if range.time_stop >= @time_stop then range.time_stop + else @time_stop + end + + @time_start = new_start + @time_stop = new_end + @commits.concat range.commits + end + + def overlap? range + result = false + + # Ref ----[----]----- + # overlapping : + # A -[----]-------- + # B -------[----]-- + # C -[----------]-- + # D ------[]------- + # non-overlapping : + # E -[]------------ + # F -----------[]-- + + start_before_start = (range.time_start < @time_start) + start_after_start = (range.time_start >= @time_start) + start_after_stop = (range.time_start >= @time_stop) + start_before_stop = (range.time_start < @time_stop) + + stop_before_stop = (range.time_stop < @time_stop) + stop_after_stop = (range.time_stop >= @time_stop) + stop_before_start = (range.time_stop < @time_start) + stop_after_start = (range.time_stop >= @time_start) + + + # A case + if start_before_start and start_before_stop and + stop_after_start and stop_before_stop then + result = true + end + + # B case + if start_after_start and start_before_stop and + stop_after_start and stop_after_stop then + result = true + end + + # C case + if start_before_start and start_before_stop and + stop_after_start and stop_after_stop then + result = true + end + + # D case + if start_after_start and start_before_stop and + stop_after_start and stop_before_stop then + result = true + end + + return result + end + + def fixed_start + return @time_start + (@config[:range_granularity]/24.0) + end + + def diff + return ("%.2f" % ((@time_stop - fixed_start).to_f * 24)).to_f + end + + def to_s + val = "(%s) %s - %s\n" % [diff, fixed_start, @time_stop] + @commits.each do |commit| + lines = [] + unless @config[:author_filter_enable] then + lines.push commit.author + end + lines.concat commit.note.split(/\n/) + r = lines.map{ |s| "\t %s" % s }.join "\n" + r[1] = '*' + val += r + "\n" + end + return val + end + end +end diff --git a/lib/timecost/rangelist.rb b/lib/timecost/rangelist.rb new file mode 100644 index 0000000..9352da6 --- /dev/null +++ b/lib/timecost/rangelist.rb @@ -0,0 +1,46 @@ + +module TimeCost + class RangeList + def initialize + @ranges = [] + end + + def add range + merged = false + merged_range = nil + + # merge + @ranges.each do |old| + #pp old + if old.overlap? range then + old.merge range + merged_range = old + merged = true + break + end + end + + # add if needed + if merged then + @ranges.delete merged_range + self.add merged_range + else + @ranges.push range + end + end + + def each + @ranges.each do |r| + yield r + end + end + + def sum + result = 0 + @ranges.each do |r| + result += r.diff + end + return result + end + end +end