#!/usr/bin/ruby # TODO: # - this uses UNIX newlines (not \n\r for windows), do i care? # == init ====================================================================== VERSION_NUM = "0.1.3" DEFAULT_FILENAME = "TIME_LOG" END_WORDS = ["done", "end", "exit", "quit"] TOTAL_STR = "\n\n#{('-' * 80)}\nTotal:\t" require 'getoptlong' # ARGV handler # parse ARGV: opts = GetoptLong.new( [ "--message", "-m", GetoptLong::REQUIRED_ARGUMENT ], [ "--file", "-f", GetoptLong::REQUIRED_ARGUMENT ], [ "--start", "-s", GetoptLong::REQUIRED_ARGUMENT ], [ "--prompt", "-p", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", "-?", GetoptLong::NO_ARGUMENT ], [ "--usage", "-u", GetoptLong::NO_ARGUMENT ], [ "--version", "-v", GetoptLong::NO_ARGUMENT ] ) # init vars to default values: task = '' @tasks = '' @message = '' @file_contents = '' @start_time = Time.now @filename = DEFAULT_FILENAME # == function definitions ====================================================== # -- I/O ------------------------------------------------------------------ # get output for a single session as a loggable string def format_output(start_time, end_time, tasks = '', message = '') minutes = minutes_between(start_time, end_time) hours = minutes.to_f / 60 # round_hours(end_time - start_time) hours = round_to_places(hours, 2) output = '' output << "#{message}\n" unless message.empty? output << "#{nice_datetime(start_time)} ...\n" output << "#{tasks}" output << "... #{nice_datetime(end_time)}\n" output << "(#{hours} hours)\n" end # append total hours worked to the end of log def append_total(log) total = calculate_total(log) log + format_total(total) end def print_usage(error_code) puts "log_time -- log work time in a time clock-like fashion" puts "" puts "Usage: log_time [OPTIONS]" puts "" puts "Valid options:" puts " -p [--prompt] : prompt user for log file and message" puts " -m [--message] \"message\" : prepend message to this work session" puts " -f [--file] log_file : file to use for log, '#{DEFAULT_FILENAME}' by default" puts " -s [--start] number : shift start time forward (+) or\n backward (-) by this many minutes" puts " -v [--version] : show the version number" puts "" puts "Messages inputted while working will be logged. A new output file will be\ncreated if one does not exist. Typing 'done', 'end', 'exit', or 'quit' will\nend a session." exit(error_code) end def version "version #{VERSION_NUM}" end # -- file handling -------------------------------------------------------- def read_file(filename) file = File.open(filename, File::RDONLY|File::CREAT) content = file.read file.close content end def overwrite_file(filename, content) file = File.open(filename, File::WRONLY|File::CREAT) result = file.puts content file.close result end # -- helpers -------------------------------------------------------------- # looks through a log and figures out total hours worked # sums all lines beginning with ( def calculate_total(log) total = 0.0 log.each_line do |line| if line[0,1] == '(' total += line[1..-1].to_f end end total end # strips the total line from the end of log # removes everything after TOTAL_STR def strip_total(log) if slice_at = log.index(TOTAL_STR) log.slice!(slice_at..-1) end log end def minutes_between(start_time, end_time) minutes_since_epoch(end_time) - minutes_since_epoch(start_time) end def minutes_since_epoch(time) time.to_i / 60 #seconds = time.to_i #seconds -= (seconds % 60) # round down to minute #seconds / 60 end def round_to_places(num, places = 2) fraction = 10 * places (num * fraction).round.to_f / fraction end def format_total(total) TOTAL_STR + total.to_s + " hours" end def nice_date(time) time.strftime("%m/%d/%y") end def nice_time(time) time.strftime('%I:%M%p') end def nice_datetime(time) nice_date(time) + " " + nice_time(time) end # -- program execution ---------------------------------------------------- # format output and write to the log def done_working(end_time = Time.now) @end_time = end_time @file_contents = strip_total(@file_contents) @file_contents << "\n\n" + format_output(@start_time, end_time, @tasks, @message) @file_contents = append_total(@file_contents) overwrite_file(@filename, @file_contents + "\n\n") end # == main ====================================================================== # parse command-line options begin opts.each do |opt, arg| case opt when "--help" print_usage(0) when "--usage" print_usage(0) when "--version" puts 'log_time : ' + version exit(0) when "--message" @message = arg when "--file" @filename = arg when "--start" @start_time += arg.to_i * 60 when "--prompt" # prompt for filename and message unless @filename != DEFAULT_FILENAME print "Log File: " @filename = gets.chomp end unless @message != '' print "Message: " @message = gets.chomp end end end rescue # the user entered an invalid option print_usage(1) end # read file @file_contents = read_file(@filename) # trap SIGINT and EXIT signals to make sure done_working gets called exit = Proc.new do done_working puts "\n-- done working at #{@end_time} ---" Kernel.exit end trap "SIGINT", Proc.new { Kernel.exit } # suppress interrupt message trap "EXIT", exit # program interaction puts "-- started working at #{@start_time} ---" begin # log any messages typed during execution time = nice_time(Time.now) @tasks << "\t[#{time}] #{task}\n" unless task.nil? or task.empty? print "\n\t" task = gets.chomp! print "\t@ #{time}" unless END_WORDS.include? task # output time of last activity end until END_WORDS.include? task