timetrackr 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/VERSION +1 -1
- data/bin/timetrackr +3 -159
- data/lib/timetrackr/cli.rb +180 -0
- data/lib/timetrackr/database.rb +103 -0
- data/lib/timetrackr/json.rb +68 -0
- data/lib/timetrackr/period.rb +12 -3
- data/lib/timetrackr/sqlite.rb +52 -51
- data/lib/timetrackr/yaml.rb +48 -47
- data/lib/timetrackr.rb +7 -89
- data/timetrackr.gemspec +8 -2
- metadata +24 -10
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/bin/timetrackr
CHANGED
@@ -1,168 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
2
3
|
|
3
4
|
lib_dir = File.dirname(__FILE__) + '/../lib'
|
4
5
|
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
5
6
|
|
6
7
|
require 'timetrackr'
|
8
|
+
require 'timetrackr/cli'
|
7
9
|
|
8
|
-
|
9
|
-
'backend' => 'yaml',
|
10
|
-
'verbose' => false,
|
11
|
-
'single_task' => false,
|
12
|
-
'path' => File.join(ENV['HOME'],'.timetrackr.db'),
|
13
|
-
'relative_format' => "%2<hours>dh %2<minutes>dm %2<seconds>ds",
|
14
|
-
'absolute_time' => "%H:%M",
|
15
|
-
'absolute_day' => "%Y-%m-%d"
|
16
|
-
}
|
17
|
-
|
18
|
-
def show_help
|
19
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
20
|
-
puts "timetrackr version #{version}"
|
21
|
-
puts <<HELP
|
22
|
-
|
23
|
-
timetrackr [command] [options]
|
24
|
-
|
25
|
-
Available commands:
|
26
|
-
|
27
|
-
start [task] start a task
|
28
|
-
stop [task] stop a task (or 'all')
|
29
|
-
switch TASK switch tasks
|
30
|
-
time [task] show time for a task (or 'all')
|
31
|
-
log [task] show time log for a task (or 'all')
|
32
|
-
|
33
|
-
Global options
|
34
|
-
-h --help show this help
|
35
|
-
-v --verbose be noisy
|
36
|
-
HELP
|
37
|
-
end
|
38
|
-
|
39
|
-
def format_time(time, fmt_str)
|
40
|
-
hours = time.to_i/3600.to_i
|
41
|
-
minutes = (time/60 - hours * 60).to_i
|
42
|
-
seconds = (time - (minutes * 60 + hours * 3600))
|
43
|
-
format(fmt_str,{
|
44
|
-
:hours => hours,
|
45
|
-
:minutes => minutes,
|
46
|
-
:seconds => seconds})
|
47
|
-
end
|
48
|
-
|
49
|
-
config = {}
|
50
|
-
config_file = File.join(ENV['HOME'],'.timetrackrrc')
|
51
|
-
if File.exist?(config_file)
|
52
|
-
require 'yaml'
|
53
|
-
config = YAML.load_file(config_file)
|
54
|
-
end
|
55
|
-
|
56
|
-
# global options
|
57
|
-
while (cmd = ARGV.shift) && cmd.start_with?('-')
|
58
|
-
if ['-v','--verbose'].include? cmd
|
59
|
-
config['verbose'] = true
|
60
|
-
end
|
61
|
-
if ['-h','--help'].include? cmd
|
62
|
-
show_help
|
63
|
-
exit 1
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
config = DEFAULTS.merge(config || {})
|
68
|
-
$verbose = config['verbose']
|
69
|
-
trackr = TimeTrackr.create(config['backend'], config)
|
70
|
-
|
71
|
-
#
|
72
|
-
# commands
|
73
|
-
#
|
74
|
-
case cmd
|
75
|
-
when 'start','in','s'
|
76
|
-
task = ARGV.shift
|
77
|
-
notes = ARGV.join(' ')
|
78
|
-
# switch tasks if config says so
|
79
|
-
if config['single_task'] && trackr.current != task
|
80
|
-
trackr.current.each { |t|
|
81
|
-
trackr.stop(t) unless t == task
|
82
|
-
}
|
83
|
-
puts "Switched to task '#{task}'" if $verbose
|
84
|
-
else
|
85
|
-
puts "Started task '#{task}'" if $verbose
|
86
|
-
end
|
87
|
-
trackr.start(task, notes)
|
88
|
-
|
89
|
-
when 'stop','out','kill','k'
|
90
|
-
if ARGV[0] == 'all' || ARGV[0].nil?
|
91
|
-
tasks = trackr.tasks
|
92
|
-
else
|
93
|
-
tasks = ARGV
|
94
|
-
end
|
95
|
-
tasks.each do |task|
|
96
|
-
trackr.stop(task)
|
97
|
-
puts "Stopped task '#{task}'" if $verbose
|
98
|
-
end
|
99
|
-
|
100
|
-
when 'switch','sw'
|
101
|
-
task = ARGV.shift
|
102
|
-
notes = ARGV.join(' ')
|
103
|
-
trackr.current.each do |t|
|
104
|
-
trackr.stop(t) unless t == task
|
105
|
-
end
|
106
|
-
trackr.start(task, notes)
|
107
|
-
puts "Switched to task '#{task}'" if $verbose
|
108
|
-
|
109
|
-
when 'time','status',nil
|
110
|
-
task = ARGV.shift
|
111
|
-
if task && trackr.tasks.include?(task)
|
112
|
-
tasks = [*task]
|
113
|
-
else
|
114
|
-
tasks = trackr.tasks.each
|
115
|
-
end
|
116
|
-
tasks.each do |task|
|
117
|
-
total = trackr.history(task).reduce(0){ |t, period|
|
118
|
-
t = t + period.length
|
119
|
-
}
|
120
|
-
name = trackr.current.include?(task) ? task+' *' : task
|
121
|
-
puts name.ljust(15) << format_time(total,config['relative_format'])
|
122
|
-
end
|
123
|
-
|
124
|
-
when 'log'
|
125
|
-
if ARGV[0] == 'all' || ARGV[0].nil?
|
126
|
-
tasks = trackr.tasks
|
127
|
-
else
|
128
|
-
tasks = ARGV
|
129
|
-
end
|
130
|
-
table = []
|
131
|
-
periods = tasks.each.collect{ |t| trackr.history(t) }.flatten
|
132
|
-
lastday = nil
|
133
|
-
table << periods.sort{|x,y| x.start <=> y.start}.collect{ |period|
|
134
|
-
currday = period.start.strftime(config['absolute_day'])
|
135
|
-
day = (currday == lastday) ? ' ' : currday
|
136
|
-
lastday = currday
|
137
|
-
name = period.current? ? period.task+' *' : period.task
|
138
|
-
start = period.start.strftime(config['absolute_time'])
|
139
|
-
stop = period.current? ? ' ' : period.stop.strftime(config['absolute_time'])
|
140
|
-
length = format_time(period.length, config['relative_format'])
|
141
|
-
notes = period.notes
|
142
|
-
"#{day.ljust(12)} #{name.ljust(15)} #{start} - #{stop.ljust(5)} #{length} #{notes}"
|
143
|
-
}
|
144
|
-
puts table
|
145
|
-
|
146
|
-
|
147
|
-
when 'clear','delete','del'
|
148
|
-
tasks = ARGV
|
149
|
-
tasks = trackr.tasks if task == 'all'
|
150
|
-
tasks.each do |task|
|
151
|
-
trackr.clear(task)
|
152
|
-
puts "Task '#{task}' cleared" if $verbose
|
153
|
-
end
|
154
|
-
|
155
|
-
when 'rename','mv'
|
156
|
-
from = ARGV.shift
|
157
|
-
to = ARGV.shift
|
158
|
-
trackr.rename(from,to)
|
159
|
-
puts "Renamed '#{from}' to '#{to}'" if $verbose
|
160
|
-
|
161
|
-
else
|
162
|
-
puts "'#{cmd}' is not a valid command"
|
163
|
-
show_help
|
164
|
-
end
|
165
|
-
|
166
|
-
trackr.close
|
10
|
+
TimeTrackr::CLI.run(ARGV)
|
167
11
|
|
168
12
|
# vim: set ts=2 sw=2 tw=80 ft=ruby fdm=syntax et fen :
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module TimeTrackr
|
2
|
+
class CLI
|
3
|
+
DEFAULTS = {
|
4
|
+
'backend' => 'yaml',
|
5
|
+
'verbose' => false,
|
6
|
+
'single_task' => false,
|
7
|
+
'path' => File.join(ENV['HOME'],'.timetrackr.db'),
|
8
|
+
'relative_format' => "%2<hours>dh %2<minutes>dm %2<seconds>ds",
|
9
|
+
'absolute_time' => "%H:%M",
|
10
|
+
'absolute_day' => "%Y-%m-%d"
|
11
|
+
}
|
12
|
+
|
13
|
+
#
|
14
|
+
# static method to get config file and run the tracker
|
15
|
+
#
|
16
|
+
def self.run(args)
|
17
|
+
config = {}
|
18
|
+
config_file = File.join(ENV['HOME'],'.timetrackrrc')
|
19
|
+
if File.exist?(config_file)
|
20
|
+
require 'yaml'
|
21
|
+
config = YAML.load_file(config_file)
|
22
|
+
end
|
23
|
+
|
24
|
+
# global options
|
25
|
+
while (cmd = args.shift) && cmd.start_with?('-')
|
26
|
+
if ['-v','--verbose'].include? cmd
|
27
|
+
config['verbose'] = true
|
28
|
+
end
|
29
|
+
if ['-h','--help'].include? cmd
|
30
|
+
cmd = 'help'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
config = DEFAULTS.merge(config || {})
|
34
|
+
|
35
|
+
cli = TimeTrackr::CLI.new(config)
|
36
|
+
cli.run(cmd, args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(config)
|
40
|
+
@config = config
|
41
|
+
@verbose = config['verbose']
|
42
|
+
@trackr = TimeTrackr::Database.create(config['backend'], config)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# run a command on the tracker
|
47
|
+
#
|
48
|
+
def run(cmd,args)
|
49
|
+
case cmd
|
50
|
+
when 'start','in','s'
|
51
|
+
task = args.shift
|
52
|
+
notes = args.join(' ')
|
53
|
+
# switch tasks if config says so
|
54
|
+
if @config['single_task'] && @trackr.current != task
|
55
|
+
@trackr.current.each { |t|
|
56
|
+
@trackr.stop(t) unless t == task
|
57
|
+
}
|
58
|
+
puts "Switched to task '#{task}'" if @verbose
|
59
|
+
else
|
60
|
+
puts "Started task '#{task}'" if @verbose
|
61
|
+
end
|
62
|
+
@trackr.start(task, notes)
|
63
|
+
|
64
|
+
when 'stop','out','kill','k'
|
65
|
+
if args[0] == 'all' || args[0].nil?
|
66
|
+
tasks = @trackr.tasks
|
67
|
+
else
|
68
|
+
tasks = args
|
69
|
+
end
|
70
|
+
tasks.each do |task|
|
71
|
+
@trackr.stop(task)
|
72
|
+
puts "Stopped task '#{task}'" if @verbose
|
73
|
+
end
|
74
|
+
|
75
|
+
when 'switch','sw'
|
76
|
+
task = args.shift
|
77
|
+
notes = args.join(' ')
|
78
|
+
@trackr.current.each do |t|
|
79
|
+
@trackr.stop(t) unless t == task
|
80
|
+
end
|
81
|
+
@trackr.start(task, notes)
|
82
|
+
puts "Switched to task '#{task}'" if @verbose
|
83
|
+
|
84
|
+
when 'time','status',nil
|
85
|
+
task = args.shift
|
86
|
+
if task && @trackr.tasks.include?(task)
|
87
|
+
tasks = [*task]
|
88
|
+
else
|
89
|
+
tasks = @trackr.tasks.each
|
90
|
+
end
|
91
|
+
tasks.each do |task|
|
92
|
+
total = @trackr.history(task).reduce(0){ |t, period|
|
93
|
+
t = t + period.length
|
94
|
+
}
|
95
|
+
name = @trackr.current.include?(task) ? task+' *' : task
|
96
|
+
puts name.ljust(15) << format_time(total,@config['relative_format'])
|
97
|
+
end
|
98
|
+
|
99
|
+
when 'log'
|
100
|
+
if args[0] == 'all' || args[0].nil?
|
101
|
+
tasks = @trackr.tasks
|
102
|
+
else
|
103
|
+
tasks = args
|
104
|
+
end
|
105
|
+
table = []
|
106
|
+
periods = tasks.each.collect{ |t| @trackr.history(t) }.flatten
|
107
|
+
lastday = nil
|
108
|
+
table << periods.sort{|x,y| x.start <=> y.start}.collect{ |period|
|
109
|
+
currday = period.start.strftime(@config['absolute_day'])
|
110
|
+
day = (currday == lastday) ? ' ' : currday
|
111
|
+
lastday = currday
|
112
|
+
name = period.current? ? period.task+' *' : period.task
|
113
|
+
start = period.start.strftime(@config['absolute_time'])
|
114
|
+
stop = period.current? ? ' ' : period.stop.strftime(@config['absolute_time'])
|
115
|
+
length = format_time(period.length, @config['relative_format'])
|
116
|
+
notes = period.notes
|
117
|
+
"#{day.ljust(12)} #{name.ljust(15)} #{start} - #{stop.ljust(5)} #{length} #{notes}"
|
118
|
+
}
|
119
|
+
puts table
|
120
|
+
|
121
|
+
|
122
|
+
when 'clear','delete','del'
|
123
|
+
tasks = args
|
124
|
+
tasks = @trackr.tasks if task == 'all'
|
125
|
+
tasks.each do |task|
|
126
|
+
@trackr.clear(task)
|
127
|
+
puts "Task '#{task}' cleared" if @verbose
|
128
|
+
end
|
129
|
+
|
130
|
+
when 'rename','mv'
|
131
|
+
from = args.shift
|
132
|
+
to = args.shift
|
133
|
+
@trackr.rename(from,to)
|
134
|
+
puts "Renamed '#{from}' to '#{to}'" if @verbose
|
135
|
+
|
136
|
+
when 'help'
|
137
|
+
show_help
|
138
|
+
|
139
|
+
else
|
140
|
+
puts "'#{cmd}' is not a valid command"
|
141
|
+
show_help
|
142
|
+
end
|
143
|
+
|
144
|
+
@trackr.close
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
def format_time(time, fmt_str)
|
150
|
+
hours = time.to_i/3600.to_i
|
151
|
+
minutes = (time/60 - hours * 60).to_i
|
152
|
+
seconds = (time - (minutes * 60 + hours * 3600))
|
153
|
+
format(fmt_str,{
|
154
|
+
:hours => hours,
|
155
|
+
:minutes => minutes,
|
156
|
+
:seconds => seconds})
|
157
|
+
end
|
158
|
+
|
159
|
+
def show_help
|
160
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
161
|
+
puts "timetrackr version #{version}"
|
162
|
+
puts <<HELP
|
163
|
+
|
164
|
+
timetrackr [command] [options]
|
165
|
+
|
166
|
+
Available commands:
|
167
|
+
|
168
|
+
start [task] start a task
|
169
|
+
stop [task] stop a task (or 'all')
|
170
|
+
switch TASK switch tasks
|
171
|
+
time [task] show time for a task (or 'all')
|
172
|
+
log [task] show time log for a task (or 'all')
|
173
|
+
|
174
|
+
Global options
|
175
|
+
-h --help show this help
|
176
|
+
-v --verbose be noisy
|
177
|
+
HELP
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
module TimeTrackr
|
3
|
+
class Database
|
4
|
+
def self.create(type, options={})
|
5
|
+
case type.to_s
|
6
|
+
when 'yaml'
|
7
|
+
begin
|
8
|
+
require 'yaml'
|
9
|
+
db = TimeTrackr::YamlDatabase.new(options['path'])
|
10
|
+
puts 'Loaded YAML tracker' if options['verbose']
|
11
|
+
rescue LoadError
|
12
|
+
puts 'YAML not found'
|
13
|
+
end
|
14
|
+
|
15
|
+
when 'sqlite'
|
16
|
+
begin
|
17
|
+
require 'sqlite3'
|
18
|
+
db = TimeTrackr::SqliteDatabase.new(options['path'])
|
19
|
+
puts 'Loaded sqlite tracker' if options['verbose']
|
20
|
+
rescue LoadError
|
21
|
+
puts 'Sqlite not found'
|
22
|
+
end
|
23
|
+
|
24
|
+
when 'json'
|
25
|
+
begin
|
26
|
+
require 'json'
|
27
|
+
db = TimeTrackr::JsonDatabase.new(options['path'])
|
28
|
+
puts 'Loaded JSON database' if options['verbose']
|
29
|
+
rescue LoadError
|
30
|
+
puts 'JSON not found'
|
31
|
+
end
|
32
|
+
|
33
|
+
else
|
34
|
+
raise "Bad log type: #{type}"
|
35
|
+
end
|
36
|
+
db
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# return an array of current tasks
|
41
|
+
#
|
42
|
+
def current
|
43
|
+
raise 'Not Implemented'
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# start a period with optional notes
|
48
|
+
#
|
49
|
+
def start(task,notes)
|
50
|
+
raise 'Not implemented'
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# stop a period
|
55
|
+
#
|
56
|
+
def stop(task)
|
57
|
+
raise 'Not implemented'
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# return an array of all tasks
|
62
|
+
#
|
63
|
+
def tasks
|
64
|
+
raise 'Not Implemented'
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# time in task in seconds
|
69
|
+
#
|
70
|
+
def time(task)
|
71
|
+
raise 'Not implemented'
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# get task history as an array of Periods
|
76
|
+
#
|
77
|
+
def history(task)
|
78
|
+
raise 'Not Implemented'
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# rename a task
|
83
|
+
#
|
84
|
+
def rename(from, to)
|
85
|
+
raise 'Not implemented'
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# clear an task
|
90
|
+
#
|
91
|
+
def clear(task)
|
92
|
+
raise 'Not Implemented'
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# cleanup and close
|
97
|
+
#
|
98
|
+
def close
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module TimeTrackr
|
2
|
+
class JsonDatabase < TimeTrackr::Database
|
3
|
+
|
4
|
+
def initialize(path)
|
5
|
+
@log_path = path
|
6
|
+
if !File.exist? @log_path
|
7
|
+
@db = {'current' => [], 'tasks' => {}}
|
8
|
+
write_file
|
9
|
+
end
|
10
|
+
File.open(@log_path,'r') do |fh|
|
11
|
+
@db = JSON.load(fh)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def current
|
16
|
+
@db['current']
|
17
|
+
end
|
18
|
+
|
19
|
+
def tasks
|
20
|
+
@db['tasks'].keys.compact.uniq || []
|
21
|
+
end
|
22
|
+
|
23
|
+
def start(task, notes)
|
24
|
+
@db['tasks'][task] = Array[] unless @db['tasks'][task]
|
25
|
+
if !@db['current'].include?(task)
|
26
|
+
@db['current'].unshift(task)
|
27
|
+
@db['tasks'][task].push({'start' => Time.now, 'notes' => notes})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop(task)
|
32
|
+
if @db['current'].include?(task)
|
33
|
+
@db['current'].delete(task)
|
34
|
+
@db['tasks'][task].last['stop'] = Time.now
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def history(task, p_begin=nil, p_end=nil)
|
39
|
+
@db['tasks'][task].sort{|x,y| x['start'] <=> y['start']}.collect {|p|
|
40
|
+
Period.new(task,p['start'],p['stop'],p['notes'])
|
41
|
+
} unless !@db['tasks'].include? task
|
42
|
+
end
|
43
|
+
|
44
|
+
def rename(from, to)
|
45
|
+
@db['tasks'][to] = @db['tasks'].delete(from)
|
46
|
+
if @db['current'].delete(from)
|
47
|
+
@db['current'].unshift(to)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
write_file
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear(task)
|
56
|
+
@db['current'].delete(task)
|
57
|
+
@db['tasks'].delete(task)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def write_file
|
63
|
+
File.open(@log_path,'w') do |fh|
|
64
|
+
JSON.dump(@db,fh)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/timetrackr/period.rb
CHANGED
@@ -9,13 +9,22 @@ class Period
|
|
9
9
|
@notes = notes
|
10
10
|
end
|
11
11
|
|
12
|
+
def start
|
13
|
+
@start.class == Time ? @start : Time.parse(@start)
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop
|
17
|
+
return nil if @stop.nil?
|
18
|
+
@stop.class == Time ? @stop : Time.parse(@stop)
|
19
|
+
end
|
20
|
+
|
12
21
|
def length
|
13
|
-
stop =
|
14
|
-
stop -
|
22
|
+
stop = self.stop || Time.now
|
23
|
+
stop - self.start
|
15
24
|
end
|
16
25
|
|
17
26
|
def current?
|
18
|
-
|
27
|
+
self.stop.nil?
|
19
28
|
end
|
20
29
|
|
21
30
|
end
|
data/lib/timetrackr/sqlite.rb
CHANGED
@@ -1,70 +1,71 @@
|
|
1
|
-
|
1
|
+
module TimeTrackr
|
2
|
+
class SqliteDatabase < TimeTrackr::Database
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
def initialize(path)
|
5
|
+
@log_path = path
|
6
|
+
if !File.exist? @log_path
|
7
|
+
@db = SQLite3::Database.new(@log_path)
|
8
|
+
sql_events = "CREATE TABLE events (
|
8
9
|
id INTEGER PRIMARY KEY,
|
9
10
|
task TEXT,
|
10
11
|
start TIME,
|
11
12
|
stop TIME,
|
12
13
|
notes TEXT);"
|
13
14
|
@db.execute(sql_events)
|
14
|
-
|
15
|
-
|
15
|
+
else
|
16
|
+
@db = SQLite3::Database.open(@log_path)
|
17
|
+
end
|
18
|
+
@db.type_translation = true
|
16
19
|
end
|
17
|
-
@db.type_translation = true
|
18
|
-
puts "Using DB file '#{@log_path}'" if $verbose
|
19
|
-
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
def current
|
22
|
+
sql = "SELECT DISTINCT task FROM events WHERE stop IS NULL;"
|
23
|
+
@db.execute(sql).collect{|row|
|
24
|
+
row.first
|
25
|
+
}
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
def tasks
|
29
|
+
sql = "SELECT DISTINCT task FROM events;"
|
30
|
+
@db.execute(sql).collect{ |row|
|
31
|
+
row.first
|
32
|
+
}
|
33
|
+
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def start(task, notes)
|
36
|
+
sql = "SELECT id FROM events WHERE task = :task AND stop IS NULL;"
|
37
|
+
exists = @db.get_first_value(sql, 'task' => task)
|
38
|
+
if !exists
|
39
|
+
sql = "INSERT INTO events (task,start,notes) VALUES (:task,:start,:notes);"
|
40
|
+
@db.execute(sql,'task' => task, 'start' => Time.now.to_s, 'notes' => notes)
|
41
|
+
end
|
41
42
|
end
|
42
|
-
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def stop(task)
|
45
|
+
sql = "SELECT id FROM events WHERE task = :task AND stop IS NULL;"
|
46
|
+
exists = @db.get_first_value(sql, 'task' => task)
|
47
|
+
if exists
|
48
|
+
sql = "UPDATE events SET stop = :stop WHERE id = :current;"
|
49
|
+
@db.execute(sql, 'current' => exists, 'stop' => Time.now.to_s)
|
50
|
+
end
|
50
51
|
end
|
51
|
-
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
def history(task, p_begin=nil, p_end=nil)
|
54
|
+
sql = "SELECT start, stop, notes FROM events WHERE task = :task ORDER BY start;"
|
55
|
+
@db.execute(sql,'task' => task).collect{ |row|
|
56
|
+
Period.new(task,row[0],row[1],row[2])
|
57
|
+
}
|
58
|
+
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
def rename(from, to)
|
61
|
+
sql = "UPDATE events SET task = :to WHERE task = :from;"
|
62
|
+
@db.execute(sql, 'to' => to, 'from' => from)
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
def clear(task)
|
66
|
+
sql = "DELETE FROM events WHERE task = :task;"
|
67
|
+
@db.execute(sql, 'task' => task)
|
68
|
+
end
|
69
69
|
|
70
|
+
end
|
70
71
|
end
|
data/lib/timetrackr/yaml.rb
CHANGED
@@ -10,68 +10,69 @@
|
|
10
10
|
# :notes: "blah blah blah"
|
11
11
|
#
|
12
12
|
|
13
|
-
|
13
|
+
module TimeTrackr
|
14
|
+
class YamlDatabase < TimeTrackr::Database
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def initialize(path)
|
17
|
+
@log_path = path
|
18
|
+
if !File.exist? @log_path
|
19
|
+
@db = {:current => [], :tasks => {}}
|
20
|
+
write_file
|
21
|
+
end
|
22
|
+
@db = YAML.load_file(@log_path)
|
20
23
|
end
|
21
|
-
@db = YAML.load_file(@log_path)
|
22
|
-
puts "Using log file '#{@log_path}'" if $verbose
|
23
|
-
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
def current
|
26
|
+
@db[:current]
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
def tasks
|
30
|
+
@db[:tasks].keys.compact.uniq || []
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
def start(task, notes)
|
34
|
+
@db[:tasks][task] = Array[] unless @db[:tasks][task]
|
35
|
+
if !@db[:current].include?(task)
|
36
|
+
@db[:current].unshift(task)
|
37
|
+
@db[:tasks][task].push({:start => Time.now, :notes => notes})
|
38
|
+
end
|
38
39
|
end
|
39
|
-
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
def stop(task)
|
42
|
+
if @db[:current].include?(task)
|
43
|
+
@db[:current].delete(task)
|
44
|
+
@db[:tasks][task].last[:stop] = Time.now
|
45
|
+
end
|
45
46
|
end
|
46
|
-
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
def history(task, p_begin=nil, p_end=nil)
|
49
|
+
@db[:tasks][task].sort{|x,y| x[:start] <=> y[:start]}.collect {|p|
|
50
|
+
Period.new(task,p[:start],p[:stop],p[:notes])
|
51
|
+
} unless !@db[:tasks].include? task
|
52
|
+
end
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
def rename(from, to)
|
55
|
+
@db[:tasks][to] = @db[:tasks].delete(from)
|
56
|
+
if @db[:current].delete(from)
|
57
|
+
@db[:current].unshift(to)
|
58
|
+
end
|
58
59
|
end
|
59
|
-
end
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
def close
|
62
|
+
write_file
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
def clear(task)
|
66
|
+
@db[:current].delete(task)
|
67
|
+
@db[:tasks].delete(task)
|
68
|
+
end
|
69
69
|
|
70
|
-
|
70
|
+
private
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
def write_file
|
73
|
+
File.open(@log_path,'w') do |fh|
|
74
|
+
YAML.dump(@db,fh)
|
75
|
+
end
|
75
76
|
end
|
76
77
|
end
|
77
78
|
end
|
data/lib/timetrackr.rb
CHANGED
@@ -1,92 +1,10 @@
|
|
1
|
-
|
2
|
-
autoload 'SqliteTimeTrackr', 'timetrackr/sqlite'
|
3
|
-
autoload 'Period', 'timetrackr/period'
|
1
|
+
module TimeTrackr
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
when 'yaml'
|
9
|
-
begin
|
10
|
-
require 'yaml'
|
11
|
-
log = YamlTimeTrackr.new(options['path'])
|
12
|
-
puts 'Loaded yaml tracker' if $verbose
|
13
|
-
rescue LoadError
|
14
|
-
puts 'Yaml not found'
|
15
|
-
end
|
16
|
-
when 'sqlite'
|
17
|
-
begin
|
18
|
-
require 'sqlite3'
|
19
|
-
log = SqliteTimeTrackr.new(options['path'])
|
20
|
-
puts 'Loaded sqlite tracker' if $verbose
|
21
|
-
rescue LoadError
|
22
|
-
puts 'Sqlite not found'
|
23
|
-
end
|
24
|
-
else
|
25
|
-
raise "Bad log type: #{type}"
|
26
|
-
end
|
27
|
-
log
|
28
|
-
end
|
29
|
-
|
30
|
-
#
|
31
|
-
# return an array of current tasks
|
32
|
-
#
|
33
|
-
def current
|
34
|
-
raise 'Not Implemented'
|
35
|
-
end
|
36
|
-
|
37
|
-
#
|
38
|
-
# start a period with optional notes
|
39
|
-
#
|
40
|
-
def start(task,notes)
|
41
|
-
raise 'Not implemented'
|
42
|
-
end
|
43
|
-
|
44
|
-
#
|
45
|
-
# stop a period
|
46
|
-
#
|
47
|
-
def stop(task)
|
48
|
-
raise 'Not implemented'
|
49
|
-
end
|
50
|
-
|
51
|
-
#
|
52
|
-
# return an array of all tasks
|
53
|
-
#
|
54
|
-
def tasks
|
55
|
-
raise 'Not Implemented'
|
56
|
-
end
|
57
|
-
|
58
|
-
#
|
59
|
-
# time in task in seconds
|
60
|
-
#
|
61
|
-
def time(task)
|
62
|
-
end
|
63
|
-
|
64
|
-
#
|
65
|
-
# get task history as an array of Periods
|
66
|
-
#
|
67
|
-
def history(task)
|
68
|
-
raise 'Not Implemented'
|
69
|
-
end
|
70
|
-
|
71
|
-
#
|
72
|
-
# rename a task
|
73
|
-
#
|
74
|
-
def rename(from, to)
|
75
|
-
raise 'Not implemented'
|
76
|
-
end
|
77
|
-
|
78
|
-
#
|
79
|
-
# clear an task
|
80
|
-
#
|
81
|
-
def clear(task)
|
82
|
-
raise 'Not Implemented'
|
83
|
-
end
|
84
|
-
|
85
|
-
#
|
86
|
-
# cleanup and close
|
87
|
-
#
|
88
|
-
def close
|
89
|
-
end
|
3
|
+
require 'time'
|
4
|
+
require 'timetrackr/database'
|
5
|
+
require 'timetrackr/period'
|
90
6
|
|
7
|
+
autoload 'YamlDatabase', 'timetrackr/yaml'
|
8
|
+
autoload 'SqliteDatabase', 'timetrackr/sqlite'
|
9
|
+
autoload 'JsonDatabase', 'timetrackr/json'
|
91
10
|
end
|
92
|
-
|
data/timetrackr.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{timetrackr}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Felix Hanley"]
|
12
|
-
s.date = %q{2011-05-
|
12
|
+
s.date = %q{2011-05-23}
|
13
13
|
s.default_executable = %q{timetrackr}
|
14
14
|
s.description = %q{A simple time tracking utility}
|
15
15
|
s.email = %q{felix@seconddrawer.com.au}
|
@@ -27,6 +27,9 @@ Gem::Specification.new do |s|
|
|
27
27
|
"VERSION",
|
28
28
|
"bin/timetrackr",
|
29
29
|
"lib/timetrackr.rb",
|
30
|
+
"lib/timetrackr/cli.rb",
|
31
|
+
"lib/timetrackr/database.rb",
|
32
|
+
"lib/timetrackr/json.rb",
|
30
33
|
"lib/timetrackr/period.rb",
|
31
34
|
"lib/timetrackr/sqlite.rb",
|
32
35
|
"lib/timetrackr/yaml.rb",
|
@@ -49,12 +52,14 @@ Gem::Specification.new do |s|
|
|
49
52
|
|
50
53
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
54
|
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<json>, [">= 0"])
|
52
56
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
53
57
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
54
58
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
55
59
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
56
60
|
else
|
57
61
|
s.add_dependency(%q<sqlite3>, [">= 0"])
|
62
|
+
s.add_dependency(%q<json>, [">= 0"])
|
58
63
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
59
64
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
60
65
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
@@ -62,6 +67,7 @@ Gem::Specification.new do |s|
|
|
62
67
|
end
|
63
68
|
else
|
64
69
|
s.add_dependency(%q<sqlite3>, [">= 0"])
|
70
|
+
s.add_dependency(%q<json>, [">= 0"])
|
65
71
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
66
72
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
73
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: timetrackr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.1.
|
5
|
+
version: 0.1.5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Felix Hanley
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-05-
|
13
|
+
date: 2011-05-23 00:00:00 +07:00
|
14
14
|
default_executable: timetrackr
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: *id001
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: json
|
29
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
@@ -36,8 +36,19 @@ dependencies:
|
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: *id002
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
|
-
name:
|
39
|
+
name: shoulda
|
40
40
|
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
41
52
|
none: false
|
42
53
|
requirements:
|
43
54
|
- - ~>
|
@@ -45,10 +56,10 @@ dependencies:
|
|
45
56
|
version: 1.0.0
|
46
57
|
type: :development
|
47
58
|
prerelease: false
|
48
|
-
version_requirements: *
|
59
|
+
version_requirements: *id004
|
49
60
|
- !ruby/object:Gem::Dependency
|
50
61
|
name: jeweler
|
51
|
-
requirement: &
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
52
63
|
none: false
|
53
64
|
requirements:
|
54
65
|
- - ~>
|
@@ -56,10 +67,10 @@ dependencies:
|
|
56
67
|
version: 1.5.2
|
57
68
|
type: :development
|
58
69
|
prerelease: false
|
59
|
-
version_requirements: *
|
70
|
+
version_requirements: *id005
|
60
71
|
- !ruby/object:Gem::Dependency
|
61
72
|
name: rcov
|
62
|
-
requirement: &
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
63
74
|
none: false
|
64
75
|
requirements:
|
65
76
|
- - ">="
|
@@ -67,7 +78,7 @@ dependencies:
|
|
67
78
|
version: "0"
|
68
79
|
type: :development
|
69
80
|
prerelease: false
|
70
|
-
version_requirements: *
|
81
|
+
version_requirements: *id006
|
71
82
|
description: A simple time tracking utility
|
72
83
|
email: felix@seconddrawer.com.au
|
73
84
|
executables:
|
@@ -86,6 +97,9 @@ files:
|
|
86
97
|
- VERSION
|
87
98
|
- bin/timetrackr
|
88
99
|
- lib/timetrackr.rb
|
100
|
+
- lib/timetrackr/cli.rb
|
101
|
+
- lib/timetrackr/database.rb
|
102
|
+
- lib/timetrackr/json.rb
|
89
103
|
- lib/timetrackr/period.rb
|
90
104
|
- lib/timetrackr/sqlite.rb
|
91
105
|
- lib/timetrackr/yaml.rb
|
@@ -106,7 +120,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
120
|
requirements:
|
107
121
|
- - ">="
|
108
122
|
- !ruby/object:Gem::Version
|
109
|
-
hash:
|
123
|
+
hash: -2538018453800347300
|
110
124
|
segments:
|
111
125
|
- 0
|
112
126
|
version: "0"
|