Sutto / tracktime

time tracking for minimalist with no bullshit.

This URL has Read+Write access

tracktime / trt
100755 99 lines (84 sloc) 2.669 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env ruby
# coding: utf-8
 
%w(rubygems yaml thor).each do |f|
  begin
    require f
  rescue LoadError => e
    $stderr.puts "Unable to load '#{f}', exiting."
    exit 1
  end
end
 
class TimeTracker < Thor
 
  PROJECT = File.basename(File.expand_path(Dir.pwd))
  CONFIG = File.expand_path("~/trt.yml")
  
  map ["-h", "-?", "--help", "-D"] => :help, ["in", "ci"] => :clockin,
      ["out", "co"] => :clockout, ["st", "s"] => :status
  
  desc 'clockin', "Clocks in to #{PROJECT} if clocked in."
  def clockin
    if clocked_in?
      puts "You are already clocked in, please clock out first."
    else
      current_shifts << {
        :description => "In Progress",
        :completed => false,
        :start_time => Time.now,
      }
      save!
      puts "Clocked in."
    end
  end
  
  desc 'clockout', "Clocks out of #{PROJECT} if clocked in."
  def clockout
    if clocked_in?
      last_shift = current_shifts.detect { |s| !s[:completed] }
      last_shift[:end_time] = Time.now
      print "Message: "
      last_shift[:description] = STDIN.gets.strip
      last_shift[:completed] = true
      save!
      puts "Clocked Out"
    else
      puts "Please clock in first."
    end
  end
  
  desc 'status', "Shows the status for #{PROJECT}"
  method_options :all => :boolean
  def status
    if options && options[:all]
      shifts.sort_by { |g| g[0] }.each do |project, s2|
        puts "Project: #{project}"
        shifts_for(s2, " ")
      end
    else
      shifts_for(current_shifts)
    end
  end
  
  protected
  
  def shifts_for(group, spacing = "")
    max_width = group.map { |s| (d = s[:description]).strip == "" ? 14 : d.length}.max
    group.each do |shift|
      st = shift[:completed] ? "✔" : "☛"
      shift[:description] = "No description" if shift[:description].nil? || shift[:description].strip == ""
      t = "#{st} #{shift[:description].ljust(max_width)} - #{shift[:start_time].strftime("%I:%M%p, %d/%m/%Y")}"
      t << " to #{shift[:end_time].strftime("%I:%M%p, %d/%m/%Y")}" if shift[:completed]
      t << " (#{((shift[:end_time] || Time.now) - shift[:start_time]).to_i / 60}m)"
      puts [spacing.to_s, t].join
    end
    if group.empty?
      puts "#{spacing.to_s}No shifts for this project."
    end
  end
  
  def current_shifts
    shifts[PROJECT] ||= []
  end
  
  def clocked_in?
    current_shifts.any? { |c| !c[:completed] }
  end
  
  def shifts
    @shifts ||= (f = YAML.load_file(CONFIG) rescue nil).is_a?(Hash) ? f : Hash.new { |h,k| h[k] = [] }
  end
  
  def save!
    File.open(CONFIG, "w+") { |f| f.write YAML.dump(@shifts) }
  end
  
end
 
ARGV.empty? ? TimeTracker.new.status : TimeTracker.start