summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfelix <felix@seconddrawer.com.au>2011-10-27 17:30:18 +0000
committerfelix <felix@seconddrawer.com.au>2011-10-27 17:30:18 +0000
commite0ed2b4eb0e699aa71cd50361e3acbf4e2739d8d (patch)
tree7821c29595a76e52ded79fb1ac8102bed79d8376
parente2a0268806fc8804329fcf0e84efc6357e41593f (diff)
downloadtimetrackr-e0ed2b4eb0e699aa71cd50361e3acbf4e2739d8d.tar.gz
timetrackr-e0ed2b4eb0e699aa71cd50361e3acbf4e2739d8d.tar.bz2
just stick to YAML
added 'summary' option to log
-rw-r--r--Gemfile6
-rw-r--r--README.mkd13
-rw-r--r--lib/timetrackr.rb5
-rw-r--r--lib/timetrackr/cli.rb103
-rw-r--r--lib/timetrackr/database.rb120
-rw-r--r--lib/timetrackr/json.rb68
-rw-r--r--lib/timetrackr/sqlite.rb71
-rw-r--r--lib/timetrackr/yaml.rb78
8 files changed, 141 insertions, 323 deletions
diff --git a/Gemfile b/Gemfile
index 9e9804d..65d7e49 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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
diff --git a/README.mkd b/README.mkd
index fbc5f00..5d0577b 100644
--- a/README.mkd
+++ b/README.mkd
@@ -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