Skip to content

Instantly share code, notes, and snippets.

@justinperkins
Created May 28, 2009 04:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinperkins/119085 to your computer and use it in GitHub Desktop.
Save justinperkins/119085 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'rubygems'
=begin
TimeTracker is a simple way to track time on tasks
Start tracking time: ./time_tracker.rb -s some-task-name
Stop tracking time: ./time_tracker.rb -f
View recorded time for a task: ./time_tracker.rb -h some-task-name
View all tasks and their
respective recorded time: ./time_tracker.rb -l
Add a task: ./time_tracker.rb -a some-task-name
Remove a task: ./time_tracker.rb -d some-task-name
Reset recorded time for a task: ./time_tracker.rb -r some-task-name
Test everything: ./time_tracker.rb -t
=end
class TimeTracker
DATA = '~/bin/.time_tracker.yml'
def initialize
@data = File.open(File.expand_path(DATA)) { |f| YAML::load(f) } rescue nil
@data ||= {}
@data[:tasks] ||= []
end
def persist
File.open(File.expand_path(DATA), 'w+') { |f| f.write(@data.to_yaml) }
end
def list_tasks
puts('no tasks') and return if @data[:tasks].empty?
results = @data[:tasks].inject([]) do |memo, task|
memo << "#{ task } (#{ hours_for(task) })"
memo
end
puts results.join(', ')
end
def add_task(task)
puts('task already exists') || return if @data[:tasks].include?(task)
@data[:tasks] << task
puts "added task #{ task }"
end
def remove_task(task)
deleted = @data[:tasks].delete(task)
puts "deleted #{ deleted || 'nothing' }"
end
def start_timer(task)
add_task(task) unless @data[:tasks].include?(task)
active = @data[:active_timer]
finish_timer if active
@data[:active_timer] = {:start => Time.now, :task => task}
puts "started timer for #{ task }"
end
def finish_timer
active = @data[:active_timer]
puts('no timer running') || return unless active
task = active[:task]
start = active[:start]
stop = Time.now
puts "stopping timer for #{ active[:task] }"
difference_in_seconds = stop - start
difference_in_hours = difference_in_seconds / 60 / 60
difference_in_hours_rounded = round_hours(difference_in_hours)
@data[:recorded_time] ||= {}
@data[:recorded_time][task.to_sym] ||= 0
@data[:recorded_time][task.to_sym] = round_hours(@data[:recorded_time][task.to_sym] + difference_in_hours_rounded)
puts "worked #{ difference_in_hours_rounded } hours, for a total of #{ @data[:recorded_time][task.to_sym] }"
@data.delete(:active_timer)
end
def hours_for(task)
@data[:recorded_time] ||= {}
@data[:recorded_time][task.to_sym] || 0
end
def reset_recorded_hours(task)
@data[:recorded_time].delete(task.to_sym) if @data[:recorded_time][task.to_sym]
end
def test
# make sure time calculations are working correctly, that's sorta important
# keep track of an active task if it's running, too clever?
running_task = @data[:active_timer][:task] if active?
test_task = 'test-task'
# reset the recorded time to zero if we have anything
reset_recorded_hours(test_task)
# make sure we get the zero we're expecting since we just reset the recorded hours for this task
puts_failure "hours for test task should be zero" if hours_for(test_task) != 0
# kick off a timer
start_timer(test_task)
# make sure the correct task has started
puts_failure "task is not correct" if @data[:active_timer][:task] != test_task
# hack the start time to something in the past
five_hours_ago = Time.now - (60*60*5)
@data[:active_timer][:start] = five_hours_ago
puts '*** imagine 5 hours has lapsed ***'
finish_timer
puts_failure 'task was not stopped like expected' if @data[:active_timer]
puts_failure 'recorded hours not correct, expected 5' if hours_for(test_task).to_i != 5
# start again, gonna verify hours are added up correctly
start_timer(test_task)
# hack the start time once again
three_hours_ago = Time.now - (60*60*3)
@data[:active_timer][:start] = three_hours_ago
puts '*** imagine 3 hours has lapsed ***'
finish_timer
puts_failure 'recorded hours not correct, expected 8' if hours_for(test_task).to_i != 8
reset_recorded_hours(test_task)
puts_failure 'hours for test task should be zero' if hours_for(test_task) != 0
remove_task(test_task)
# restart the previously running task if we've got one
start_timer(running_task) if running_task
end
def active?
!@data[:active_timer].nil?
end
private
def puts_failure(string)
puts "TEST FAILURE: #{ string }"
end
def round_hours(hours)
(hours * 10 ** 2).round.to_f / 10 ** 2
end
end
if $0 == __FILE__
time = TimeTracker.new
command = ARGV.first
if %w{ -a -d -s -h -r }.include?(command)
task = ARGV[1]
unless task
puts 'expected task name'
exit(0)
end
end
case command
when '-l'
time.list_tasks
when '-a'
time.add_task(task)
when '-d'
time.remove_task(task)
when '-s'
time.start_timer(task)
when '-h'
puts "total hours for task #{ task } is: #{ time.hours_for(task) }"
when '-f'
time.finish_timer
when '-r'
time.reset_recorded_hours(task)
when '-t'
time.test
else
puts 'nothing to do, use -l (list), -a [task name] (add task), -d [task name] (delete task), -s [task name] (start timer for task), -h [task name] (recorded hours for task), -f (finish active timer), -r [task name] (reset recorded hours for given task), -t (test logic)'
end
time.persist
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment