220 lines
5 KiB
Ruby
220 lines
5 KiB
Ruby
module TimeCost
|
|
class CLI
|
|
def initialize
|
|
# FIXME: accept multiple authors
|
|
@config = {
|
|
:author_filter_enable => false,
|
|
:author_filter => ".*?",
|
|
|
|
:date_filter_enable => false,
|
|
:date_filter => [],
|
|
|
|
:branches_filter_enable => true,
|
|
|
|
:input_dump => [],
|
|
:output_dump => nil,
|
|
|
|
:range_granularity => 0.5, # in decimal hours
|
|
|
|
:verbose => false
|
|
}
|
|
@rangelist = {}
|
|
@authorlist = nil
|
|
end
|
|
|
|
def parse_cmdline args
|
|
options = 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("--before DATE", "Keep only commits before DATE") do |date|
|
|
puts "set date filter to <= #{date}"
|
|
@config[:date_filter] << lambda { |other|
|
|
return (other <= DateTime.parse(date))
|
|
}
|
|
@config[:date_filter_enable] = true
|
|
end
|
|
|
|
opts.on("--after DATE", "Keep only commits after DATE") do |date|
|
|
puts "set date filter to >= #{date}"
|
|
@config[:date_filter] << lambda { |other|
|
|
return (other >= DateTime.parse(date))
|
|
}
|
|
@config[:date_filter_enable] = true
|
|
end
|
|
|
|
opts.on("-t","--time TIME", "Keep only commits on last TIME days") 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
|
|
|
|
opts.on_tail("--all", "Collect from all branches and refs") do
|
|
@config[:branches_filter_enable] = false
|
|
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
|
|
options.parse! args
|
|
|
|
end
|
|
|
|
|
|
def analyze_git
|
|
# git log
|
|
# foreach, create time range (before) + logs
|
|
|
|
cmd = [
|
|
"git", "log",
|
|
"--date=iso",
|
|
"--no-patch"
|
|
]
|
|
if not @config[:branches_filter_enable] then
|
|
cmd << "--all"
|
|
end
|
|
cmd.concat ["--", "."]
|
|
process = IO.popen cmd
|
|
|
|
@rangelist = {}
|
|
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 commit, granularity: @config[:range_granularity]
|
|
|
|
if not @rangelist.include? commit.author then
|
|
@rangelist[commit.author] = RangeList.new
|
|
end
|
|
@rangelist[commit.author].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
|
|
|
|
# reject if a some filter does not validate date
|
|
filter_keep = true
|
|
filters = @config[:date_filter]
|
|
filters.each do |f|
|
|
filter_keep &= f.call(DateTime.parse(commit.date))
|
|
end
|
|
|
|
if not filter_keep then
|
|
commit = nil
|
|
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 |author,rangelist|
|
|
rangelist.each do |range|
|
|
puts range.to_s(!@config[:author_filter_enable]) + "\n"
|
|
end
|
|
end
|
|
total = 0
|
|
@rangelist.each do |author,rangelist|
|
|
puts "SUB-TOTAL for %s: %.2f hours\n" % [author, rangelist.sum]
|
|
total += rangelist.sum
|
|
end
|
|
puts "TOTAL: %.2f hours" % total
|
|
end
|
|
end
|
|
end
|
|
|