timetrackr 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|