diff options
| author | felix <felix@seconddrawer.com.au> | 2011-10-27 17:30:18 +0000 |
|---|---|---|
| committer | felix <felix@seconddrawer.com.au> | 2011-10-27 17:30:18 +0000 |
| commit | e0ed2b4eb0e699aa71cd50361e3acbf4e2739d8d (patch) | |
| tree | 7821c29595a76e52ded79fb1ac8102bed79d8376 | |
| parent | e2a0268806fc8804329fcf0e84efc6357e41593f (diff) | |
| download | timetrackr-e0ed2b4eb0e699aa71cd50361e3acbf4e2739d8d.tar.gz timetrackr-e0ed2b4eb0e699aa71cd50361e3acbf4e2739d8d.tar.bz2 | |
just stick to YAML
added 'summary' option to log
| -rw-r--r-- | Gemfile | 6 | ||||
| -rw-r--r-- | README.mkd | 13 | ||||
| -rw-r--r-- | lib/timetrackr.rb | 5 | ||||
| -rw-r--r-- | lib/timetrackr/cli.rb | 103 | ||||
| -rw-r--r-- | lib/timetrackr/database.rb | 120 | ||||
| -rw-r--r-- | lib/timetrackr/json.rb | 68 | ||||
| -rw-r--r-- | lib/timetrackr/sqlite.rb | 71 | ||||
| -rw-r--r-- | lib/timetrackr/yaml.rb | 78 |
8 files changed, 141 insertions, 323 deletions
@@ -1,10 +1,8 @@ source 'http://rubygems.org' group :development do - gem 'sqlite3' - gem 'json' gem 'shoulda', '>= 0' - gem 'bundler', '~> 1.0.0' - gem 'jeweler', '~> 1.5.2' + gem 'bundler', '>= 1.0.0' + gem 'jeweler', '>= 1.5.2' gem 'rcov', '>= 0' end @@ -61,13 +61,18 @@ start a task: ## Contributing to timetrackr -* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet -* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it +* Check out the latest master to make sure the feature hasn't been implemented + or the bug hasn't been fixed yet +* Check out the issue tracker to make sure someone already hasn't requested it + and/or contributed it * Fork the project * Start a feature/bugfix branch * Commit and push until you are happy with your contribution -* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. -* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. +* Make sure to add tests for it. This is important so I don't break it in a + future version unintentionally. +* Please try not to mess with the Rakefile, version, or history. If you want to + have your own version, or is otherwise necessary, that is fine, but please + isolate to its own commit so I can cherry-pick around it. ## Copyright diff --git a/lib/timetrackr.rb b/lib/timetrackr.rb index 7a5b17e..bd54c8c 100644 --- a/lib/timetrackr.rb +++ b/lib/timetrackr.rb @@ -1,10 +1,5 @@ module TimeTrackr - require 'time' require 'timetrackr/database' require 'timetrackr/period' - - autoload 'YamlDatabase', 'timetrackr/yaml' - autoload 'SqliteDatabase', 'timetrackr/sqlite' - autoload 'JsonDatabase', 'timetrackr/json' end diff --git a/lib/timetrackr/cli.rb b/lib/timetrackr/cli.rb index 1be4d1c..bd080e5 100644 --- a/lib/timetrackr/cli.rb +++ b/lib/timetrackr/cli.rb @@ -1,9 +1,8 @@ module TimeTrackr class CLI DEFAULTS = { - 'backend' => 'yaml', 'verbose' => false, - 'single_task' => false, + 'single_task' => true, 'path' => File.join(ENV['HOME'],'.timetrackr.db'), 'relative_format' => "%2<hours>dh %2<minutes>dm %2<seconds>ds", 'absolute_time' => "%H:%M", @@ -38,8 +37,7 @@ module TimeTrackr def initialize(config) @config = config - @verbose = config['verbose'] - @trackr = TimeTrackr::Database.create(config['backend'], config) + @trackr = TimeTrackr::Database.new(config) end # @@ -56,17 +54,16 @@ module TimeTrackr @trackr.current.each { |t| @trackr.stop(t) unless t == task } - puts "Switched to task '#{task}'" if @verbose + puts "Switched to task '#{task}'" if @config['verbose'] else - puts "Started task '#{task}'" if @verbose + puts "Started task '#{task}'" if @config['verbose'] end @trackr.start(task, notes) when 'stop','out','kill','k' - tasks = get_tasks(args) - tasks.each do |task| + get_tasks(args).each do |task| @trackr.stop(task) - puts "Stopped task '#{task}'" if @verbose + puts "Stopped task '#{task}'" if @config['verbose'] end when 'switch','sw' @@ -76,34 +73,44 @@ module TimeTrackr @trackr.stop(t) unless t == task end @trackr.start(task, notes) - puts "Switched to task '#{task}'" if @verbose + puts "Switched to task '#{task}'" if @config['verbose'] when 'time','status',nil tasks = get_tasks(args) puts create_log(tasks,'t') when 'log' - group = args.shift[1] if ['-d','-t'].include?(args[0]) + group = args.shift[1] if ['-d','-t','-s'].include?(args[0]) tasks = get_tasks(args) puts create_log(tasks,group) when 'clear','delete','del' - tasks = get_tasks(args) - tasks.each do |task| + get_tasks(args).each do |task| @trackr.clear(task) - puts "Task '#{task}' cleared" if @verbose + puts "Task '#{task}' cleared" if @config['verbose'] end when 'rename','mv' from = args.shift to = args.shift @trackr.rename(from,to) - puts "Renamed '#{from}' to '#{to}'" if @verbose + puts "Renamed '#{from}' to '#{to}'" if @config['verbose'] + + when 'mark','note','n' + notes = args.join(' ') + @trackr.current.each do |t| + @trackr.stop(t) + @trackr.start(t, notes) + end + puts "Annotated task(s) '#{@trackr.current.join(' ')}'" if @config['verbose'] when 'help' show_help + when 'config' + puts @config.to_yaml + else puts "'#{cmd}' is not a valid command" show_help @@ -115,10 +122,12 @@ module TimeTrackr protected def get_tasks(args) + # if 'all' add them all to the start if args[0].nil? || args[0] == 'all' || args[0] == '-n' args.unshift(@trackr.tasks).flatten!.delete('all') end + # any negated tasks? split = args.index('-n') || args.length show = args.slice(0...split).compact.uniq ignore =[*args.slice(split+1..-1)].compact.uniq @@ -136,40 +145,49 @@ module TimeTrackr end def create_log(tasks,group=nil) - totals = Hash.new(0) - days = {} + unless group.nil? + totals = Hash.new(0) + days = {} + end table = [] - # get all periods for selected tasks - periods = tasks.each.collect{ |t| @trackr.history(t) }.flatten lastday = nil - periods.sort{|x,y| x.start <=> y.start}.collect do |period| + + # get all periods for selected tasks + tasks.each.collect{ |t| @trackr.history(t) }.flatten.sort{ |x,y| x.start <=> y.start }.collect do |period| currday = period.start.strftime(@config['absolute_day']) if currday == lastday day = '' else day = currday - days[day] = Hash.new(0) + days[day] = Hash.new(0) unless group.nil? end lastday = currday - start = period.start.strftime(@config['absolute_time']) - stop = period.current? ? ' ' : period.stop.strftime(@config['absolute_time']) - name = period.current? ? period.task+' *' : period.task - notes = period.notes - length = format_time(period.length, @config['relative_format']) - - totals[period.task] = totals[period.task] + period.length if group == 't' - days[currday][period.task] = days[currday][period.task] + period.length if group == 'd' - # for full log - table << "#{day.ljust(12)} #{name.ljust(15)} #{start} - #{stop.ljust(5)} #{length} #{notes}" if group.nil? + if group.nil? + # full log + name = period.current? ? period.task+' *' : period.task + start = period.start.strftime(@config['absolute_time']) + stop = period.current? ? ' ' : period.stop.strftime(@config['absolute_time']) + length = format_time(period.length, @config['relative_format']) + notes = period.notes + table << "#{day.ljust(12)} #{name.ljust(15)} #{start} - #{stop.ljust(5)} #{length} #{notes}" + + elsif group == 's' + days[currday]['summary'] = days[currday]['summary'] + period.length + else + totals[period.task] = totals[period.task] + period.length + days[currday][period.task] = days[currday][period.task] + period.length + end end + # build the table for groupings case group when 't' tasks.each do |task| name = @trackr.current.include?(task) ? task+' *' : task table << name.ljust(15) + format_time(totals[task],@config['relative_format']) end + when 'd' prev_date = '' days.each_pair do |date,tasks| @@ -180,7 +198,14 @@ module TimeTrackr table << "#{date_string.ljust(12)} #{name.ljust(15)} " + format_time(length, @config['relative_format']) end end + + when 's' + days.each_pair do |date,day| + table << "#{date.ljust(12)} " + format_time(day['summary'], @config['relative_format']) + end end + + # spit it out table end @@ -193,11 +218,17 @@ module TimeTrackr Available commands: - start [task] start a task - stop [task] stop a task (or 'all') - switch TASK switch tasks - time [task] show time for a task (or 'all') - log [task] show time log for a task (or 'all') + start TASK start a task + stop [TASK] stop a task (or 'all') + switch TASK switch tasks + log [TASK] show time log for a task (or 'all') + -d group by day + -t group by task + -s summary (by day, no tasks) + time [TASK] same as 'log -t' + mark NOTES add notes to all current tasks + rename OLD NEW rename a task + config current config Global options -h --help show this help diff --git a/lib/timetrackr/database.rb b/lib/timetrackr/database.rb index 3c728ea..d0394ac 100644 --- a/lib/timetrackr/database.rb +++ b/lib/timetrackr/database.rb @@ -1,103 +1,109 @@ +# +# keeps the following format in a yaml file: +# +# :current: [] +# +# :tasks: +# foo: +# - :start: 2011-05-16 14:26:26.263449 +07:00 +# :stop: 2011-05-16 14:26:27 +07:00 +# :notes: "blah blah blah" +# module TimeTrackr class Database - def self.create(type, options={}) - case type.to_s - when 'yaml' - begin - require 'yaml' - db = TimeTrackr::YamlDatabase.new(options['path']) - puts 'Loaded YAML tracker' if options['verbose'] - rescue LoadError - puts 'YAML not found' + def initialize(options={}) + @options = options + begin + require 'yaml' + @log_path = options['path'] + if !File.exist? @log_path + @db = {:current => [], :tasks => {}} + write_file end - - when 'sqlite' - begin - require 'sqlite3' - db = TimeTrackr::SqliteDatabase.new(options['path']) - puts 'Loaded sqlite tracker' if options['verbose'] - rescue LoadError - puts 'Sqlite not found' - end - - when 'json' - begin - require 'json' - db = TimeTrackr::JsonDatabase.new(options['path']) - puts 'Loaded JSON database' if options['verbose'] - rescue LoadError - puts 'JSON not found' - end - - else - raise "Bad log type: #{type}" + @db = YAML.load_file(@log_path) + puts 'Loaded database' if options['verbose'] + rescue LoadError + puts 'YAML not found' end - db end # # return an array of current tasks # def current - raise 'Not Implemented' + @db[:current] end # - # start a period with optional notes - # - def start(task,notes) - raise 'Not implemented' - end - - # - # stop a period + # return an array of all tasks # - def stop(task) - raise 'Not implemented' + def tasks + @db[:tasks].keys.compact.uniq || [] end # - # return an array of all tasks + # start a period with optional notes # - def tasks - raise 'Not Implemented' + def start(task, notes) + @db[:tasks][task] = Array[] unless @db[:tasks][task] + if !@db[:current].include?(task) + @db[:current].unshift(task) + @db[:tasks][task].push({:start => Time.now, :notes => notes}) + end end # - # time in task in seconds + # stop a period # - def time(task) - raise 'Not implemented' + def stop(task) + if @db[:current].include?(task) + @db[:current].delete(task) + @db[:tasks][task].last[:stop] = Time.now + end end # # get task history as an array of Periods # - def history(task) - raise 'Not Implemented' + def history(task, p_begin=nil, p_end=nil) + @db[:tasks][task].sort{|x,y| x[:start] <=> y[:start]}.collect {|p| + Period.new(task,p[:start],p[:stop],p[:notes]) + } unless !@db[:tasks].include? task end # # rename a task # def rename(from, to) - raise 'Not implemented' + @db[:tasks][to] = @db[:tasks].delete(from) + if @db[:current].delete(from) + @db[:current].unshift(to) + end end # - # clear an task + # cleanup and close # - def clear(task) - raise 'Not Implemented' + def close + write_file end # - # cleanup and close + # clear an task # - def close + def clear(task) + @db[:current].delete(task) + @db[:tasks].delete(task) end + private + + def write_file + File.open(@log_path,'w') do |fh| + YAML.dump(@db,fh) + puts 'Saved database' if @options['verbose'] + end + end end end - diff --git a/lib/timetrackr/json.rb b/lib/timetrackr/json.rb deleted file mode 100644 index 9b919d1..0000000 --- a/lib/timetrackr/json.rb +++ /dev/null @@ -1,68 +0,0 @@ -module TimeTrackr - class JsonDatabase < TimeTrackr::Database - - def initialize(path) - @log_path = path - if !File.exist? @log_path - @db = {'current' => [], 'tasks' => {}} - write_file - end - File.open(@log_path,'r') do |fh| - @db = JSON.load(fh) - end - end - - def current - @db['current'] - end - - def tasks - @db['tasks'].keys.compact.uniq || [] - end - - def start(task, notes) - @db['tasks'][task] = Array[] unless @db['tasks'][task] - if !@db['current'].include?(task) - @db['current'].unshift(task) - @db['tasks'][task].push({'start' => Time.now, 'notes' => notes}) - end - end - - def stop(task) - if @db['current'].include?(task) - @db['current'].delete(task) - @db['tasks'][task].last['stop'] = Time.now - end - end - - def history(task, p_begin=nil, p_end=nil) - @db['tasks'][task].sort{|x,y| x['start'] <=> y['start']}.collect {|p| - Period.new(task,p['start'],p['stop'],p['notes']) - } unless !@db['tasks'].include? task - end - - def rename(from, to) - @db['tasks'][to] = @db['tasks'].delete(from) - if @db['current'].delete(from) - @db['current'].unshift(to) - end - end - - def close - write_file - end - - def clear(task) - @db['current'].delete(task) - @db['tasks'].delete(task) - end - - private - - def write_file - File.open(@log_path,'w') do |fh| - JSON.dump(@db,fh) - end - end - end -end diff --git a/lib/timetrackr/sqlite.rb b/lib/timetrackr/sqlite.rb deleted file mode 100644 index 0d4b811..0000000 --- a/lib/timetrackr/sqlite.rb +++ /dev/null @@ -1,71 +0,0 @@ -module TimeTrackr - class SqliteDatabase < TimeTrackr::Database - - def initialize(path) - @log_path = path - if !File.exist? @log_path - @db = SQLite3::Database.new(@log_path) - sql_events = "CREATE TABLE events ( - id INTEGER PRIMARY KEY, - task TEXT, - start TIME, - stop TIME, - notes TEXT);" - @db.execute(sql_events) - else - @db = SQLite3::Database.open(@log_path) - end - @db.type_translation = true - end - - def current - sql = "SELECT DISTINCT task FROM events WHERE stop IS NULL;" - @db.execute(sql).collect{|row| - row.first - } - end - - def tasks - sql = "SELECT DISTINCT task FROM events;" - @db.execute(sql).collect{ |row| - row.first - } - end - - def start(task, notes) - sql = "SELECT id FROM events WHERE task = :task AND stop IS NULL;" - exists = @db.get_first_value(sql, 'task' => task) - if !exists - sql = "INSERT INTO events (task,start,notes) VALUES (:task,:start,:notes);" - @db.execute(sql,'task' => task, 'start' => Time.now.to_s, 'notes' => notes) - end - end - - def stop(task) - sql = "SELECT id FROM events WHERE task = :task AND stop IS NULL;" - exists = @db.get_first_value(sql, 'task' => task) - if exists - sql = "UPDATE events SET stop = :stop WHERE id = :current;" - @db.execute(sql, 'current' => exists, 'stop' => Time.now.to_s) - end - end - - def history(task, p_begin=nil, p_end=nil) - sql = "SELECT start, stop, notes FROM events WHERE task = :task ORDER BY start;" - @db.execute(sql,'task' => task).collect{ |row| - Period.new(task,row[0],row[1],row[2]) - } - end - - def rename(from, to) - sql = "UPDATE events SET task = :to WHERE task = :from;" - @db.execute(sql, 'to' => to, 'from' => from) - end - - def clear(task) - sql = "DELETE FROM events WHERE task = :task;" - @db.execute(sql, 'task' => task) - end - - end -end diff --git a/lib/timetrackr/yaml.rb b/lib/timetrackr/yaml.rb deleted file mode 100644 index 787415e..0000000 --- a/lib/timetrackr/yaml.rb +++ /dev/null @@ -1,78 +0,0 @@ -# -# keeps the following format in a yaml file: -# -# :current: [] -# -# :tasks: -# foo: -# - :start: 2011-05-16 14:26:26.263449 +07:00 -# :stop: 2011-05-16 14:26:27 +07:00 -# :notes: "blah blah blah" -# - -module TimeTrackr - class YamlDatabase < TimeTrackr::Database - - def initialize(path) - @log_path = path - if !File.exist? @log_path - @db = {:current => [], :tasks => {}} - write_file - end - @db = YAML.load_file(@log_path) - end - - def current - @db[:current] - end - - def tasks - @db[:tasks].keys.compact.uniq || [] - end - - def start(task, notes) - @db[:tasks][task] = Array[] unless @db[:tasks][task] - if !@db[:current].include?(task) - @db[:current].unshift(task) - @db[:tasks][task].push({:start => Time.now, :notes => notes}) - end - end - - def stop(task) - if @db[:current].include?(task) - @db[:current].delete(task) - @db[:tasks][task].last[:stop] = Time.now - end - end - - def history(task, p_begin=nil, p_end=nil) - @db[:tasks][task].sort{|x,y| x[:start] <=> y[:start]}.collect {|p| - Period.new(task,p[:start],p[:stop],p[:notes]) - } unless !@db[:tasks].include? task - end - - def rename(from, to) - @db[:tasks][to] = @db[:tasks].delete(from) - if @db[:current].delete(from) - @db[:current].unshift(to) - end - end - - def close - write_file - end - - def clear(task) - @db[:current].delete(task) - @db[:tasks].delete(task) - end - - private - - def write_file - File.open(@log_path,'w') do |fh| - YAML.dump(@db,fh) - end - end - end -end |
