timetrackr 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/Gemfile +3 -6
- data/Gemfile.lock +5 -16
- data/{LICENSE.txt → LICENSE} +0 -0
- data/Manifest +16 -0
- data/{README.mkd → README} +9 -4
- data/Rakefile +99 -50
- data/TODO +2 -0
- data/lib/timetrackr.rb +0 -5
- data/lib/timetrackr/cli.rb +67 -36
- data/lib/timetrackr/database.rb +63 -57
- metadata +24 -97
- data/VERSION +0 -1
- data/lib/timetrackr/json.rb +0 -68
- data/lib/timetrackr/sqlite.rb +0 -71
- data/lib/timetrackr/yaml.rb +0 -78
- data/timetrackr.gemspec +0 -77
data/CHANGELOG
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= v0.2
|
2
|
+
|
3
|
+
* clean up and simplify
|
4
|
+
* removed JSON and SQLite support, only YAML
|
5
|
+
* added 'notes' command
|
6
|
+
|
7
|
+
= v0.1.6
|
8
|
+
|
9
|
+
* group by days or tasks
|
10
|
+
|
11
|
+
= v0.1.5
|
12
|
+
|
13
|
+
* added JSON database
|
14
|
+
|
15
|
+
= v0.1.4
|
16
|
+
|
17
|
+
* more commands
|
18
|
+
|
19
|
+
= v0.1.3
|
20
|
+
|
21
|
+
* stop playing with jeweller and release
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,24 +1,13 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
5
|
-
|
6
|
-
bundler (~> 1.0.0)
|
7
|
-
git (>= 1.2.5)
|
8
|
-
rake
|
9
|
-
json (1.5.1)
|
10
|
-
rake (0.8.7)
|
11
|
-
rcov (0.9.9)
|
12
|
-
shoulda (2.11.3)
|
13
|
-
sqlite3 (1.3.3)
|
4
|
+
gemcutter (0.7.0)
|
5
|
+
rake (0.9.2.2)
|
14
6
|
|
15
7
|
PLATFORMS
|
16
8
|
ruby
|
17
9
|
|
18
10
|
DEPENDENCIES
|
19
|
-
bundler (
|
20
|
-
|
21
|
-
|
22
|
-
rcov
|
23
|
-
shoulda
|
24
|
-
sqlite3
|
11
|
+
bundler (>= 1.0.0)
|
12
|
+
gemcutter
|
13
|
+
rake
|
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/Manifest
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
Gemfile
|
3
|
+
Gemfile.lock
|
4
|
+
LICENSE
|
5
|
+
Manifest
|
6
|
+
README
|
7
|
+
Rakefile
|
8
|
+
TODO
|
9
|
+
bin/timetrackr
|
10
|
+
lib/timetrackr.rb
|
11
|
+
lib/timetrackr/cli.rb
|
12
|
+
lib/timetrackr/database.rb
|
13
|
+
lib/timetrackr/period.rb
|
14
|
+
test/helper.rb
|
15
|
+
test/test_timetrackr.rb
|
16
|
+
timetrackr.gemspec
|
data/{README.mkd → README}
RENAMED
@@ -61,13 +61,18 @@ start a task:
|
|
61
61
|
|
62
62
|
## Contributing to timetrackr
|
63
63
|
|
64
|
-
* Check out the latest master to make sure the feature hasn't been implemented
|
65
|
-
|
64
|
+
* Check out the latest master to make sure the feature hasn't been implemented
|
65
|
+
or the bug hasn't been fixed yet
|
66
|
+
* Check out the issue tracker to make sure someone already hasn't requested it
|
67
|
+
and/or contributed it
|
66
68
|
* Fork the project
|
67
69
|
* Start a feature/bugfix branch
|
68
70
|
* Commit and push until you are happy with your contribution
|
69
|
-
* Make sure to add tests for it. This is important so I don't break it in a
|
70
|
-
|
71
|
+
* Make sure to add tests for it. This is important so I don't break it in a
|
72
|
+
future version unintentionally.
|
73
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to
|
74
|
+
have your own version, or is otherwise necessary, that is fine, but please
|
75
|
+
isolate to its own commit so I can cherry-pick around it.
|
71
76
|
|
72
77
|
|
73
78
|
## Copyright
|
data/Rakefile
CHANGED
@@ -1,53 +1,102 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
3
|
+
require "rubygems/package_task"
|
4
|
+
require "rdoc/task"
|
5
|
+
require "rake/testtask"
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => ["test"]
|
13
|
+
|
14
|
+
# This builds the actual gem. For details of what all these options
|
15
|
+
# mean, and other ones you can add, check the documentation here:
|
16
|
+
#
|
17
|
+
# http://rubygems.org/read/chapter/20
|
18
|
+
#
|
19
|
+
spec = Gem::Specification.new do |s|
|
20
|
+
|
21
|
+
# Change these as appropriate
|
22
|
+
s.name = "timetrackr"
|
23
|
+
s.version = "0.2.0"
|
24
|
+
s.summary = "A simple time tracking utility"
|
25
|
+
s.author = "Felix Hanley"
|
26
|
+
s.email = "felix@seconddrawer.com.au"
|
27
|
+
s.homepage = "http://github.com/felix/timetrackr"
|
28
|
+
|
29
|
+
s.has_rdoc = true
|
30
|
+
s.extra_rdoc_files = %w(README)
|
31
|
+
s.rdoc_options = %w(--main README)
|
32
|
+
|
33
|
+
# Add any extra files to include in the gem
|
34
|
+
s.files = %w(Rakefile Gemfile Gemfile.lock LICENSE README CHANGELOG Manifest TODO) + Dir.glob("{bin,test,lib}/**/*")
|
35
|
+
s.executables = FileList["bin/**"].map { |f| File.basename(f) }
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
|
38
|
+
# If you want to depend on other gems, add them here, along with any
|
39
|
+
# relevant versions
|
40
|
+
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
41
|
+
|
42
|
+
# If your tests use any gems, include them here
|
43
|
+
# s.add_development_dependency("mocha") # for example
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'Tag the repository in git with gem version number'
|
47
|
+
task :tag => [:gemspec, :package] do
|
48
|
+
if `git diff --cached`.empty?
|
49
|
+
if `git tag`.split("\n").include?("v#{spec.version}")
|
50
|
+
raise "Version #{spec.version} has already been released"
|
51
|
+
end
|
52
|
+
`git add #{File.expand_path("../#{spec.name}.gemspec",
|
53
|
+
__FILE__)}`
|
54
|
+
`git commit -m "Released version #{spec.version}"`
|
55
|
+
`git tag v#{spec.version}`
|
56
|
+
`git push --tags`
|
57
|
+
`git push`
|
58
|
+
else
|
59
|
+
raise "Unstaged changes still waiting to be committed"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Tag and publish the gem to rubygems.org"
|
64
|
+
task :publish => :tag do
|
65
|
+
`gem push pkg/#{spec.name}-#{spec.version}.gem`
|
66
|
+
end
|
67
|
+
|
68
|
+
# This task actually builds the gem. We also regenerate a static
|
69
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
70
|
+
# be automatically building a gem for this project. If you're not
|
71
|
+
# using GitHub, edit as appropriate.
|
72
|
+
#
|
73
|
+
# To publish your gem online, install the 'gemcutter' gem; Read more
|
74
|
+
# about that here: http://gemcutter.org/pages/gem_docs
|
75
|
+
Gem::PackageTask.new(spec) do |pkg|
|
76
|
+
pkg.gem_spec = spec
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
80
|
+
task :gemspec do
|
81
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
82
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
83
|
+
end
|
84
|
+
|
85
|
+
# If you don't want to generate the .gemspec file, just remove this line. Reasons
|
86
|
+
# why you might want to generate a gemspec:
|
87
|
+
# - using bundler with a git source
|
88
|
+
# - building the gem without rake (i.e. gem build blah.gemspec)
|
89
|
+
# - maybe others?
|
90
|
+
task :package => :gemspec
|
91
|
+
|
92
|
+
# Generate documentation
|
93
|
+
RDoc::Task.new do |rd|
|
94
|
+
rd.main = "README"
|
95
|
+
rd.rdoc_files.include("README", "lib/**/*.rb")
|
96
|
+
rd.rdoc_dir = "rdoc"
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'Clear out RDoc and generated packages'
|
100
|
+
task :clean => [:clobber_rdoc, :clobber_package] do
|
101
|
+
rm "#{spec.name}.gemspec"
|
53
102
|
end
|
data/lib/timetrackr.rb
CHANGED
data/lib/timetrackr/cli.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
module TimeTrackr
|
2
2
|
class CLI
|
3
3
|
DEFAULTS = {
|
4
|
-
'backend' => 'yaml',
|
5
4
|
'verbose' => false,
|
6
|
-
'single_task' =>
|
5
|
+
'single_task' => true,
|
7
6
|
'path' => File.join(ENV['HOME'],'.timetrackr.db'),
|
8
7
|
'relative_format' => "%2<hours>dh %2<minutes>dm %2<seconds>ds",
|
9
8
|
'absolute_time' => "%H:%M",
|
@@ -38,8 +37,7 @@ module TimeTrackr
|
|
38
37
|
|
39
38
|
def initialize(config)
|
40
39
|
@config = config
|
41
|
-
@
|
42
|
-
@trackr = TimeTrackr::Database.create(config['backend'], config)
|
40
|
+
@trackr = TimeTrackr::Database.new(config)
|
43
41
|
end
|
44
42
|
|
45
43
|
#
|
@@ -56,17 +54,16 @@ module TimeTrackr
|
|
56
54
|
@trackr.current.each { |t|
|
57
55
|
@trackr.stop(t) unless t == task
|
58
56
|
}
|
59
|
-
puts "Switched to task '#{task}'" if @verbose
|
57
|
+
puts "Switched to task '#{task}'" if @config['verbose']
|
60
58
|
else
|
61
|
-
puts "Started task '#{task}'" if @verbose
|
59
|
+
puts "Started task '#{task}'" if @config['verbose']
|
62
60
|
end
|
63
61
|
@trackr.start(task, notes)
|
64
62
|
|
65
63
|
when 'stop','out','kill','k'
|
66
|
-
|
67
|
-
tasks.each do |task|
|
64
|
+
get_tasks(args).each do |task|
|
68
65
|
@trackr.stop(task)
|
69
|
-
puts "Stopped task '#{task}'" if @verbose
|
66
|
+
puts "Stopped task '#{task}'" if @config['verbose']
|
70
67
|
end
|
71
68
|
|
72
69
|
when 'switch','sw'
|
@@ -76,34 +73,44 @@ module TimeTrackr
|
|
76
73
|
@trackr.stop(t) unless t == task
|
77
74
|
end
|
78
75
|
@trackr.start(task, notes)
|
79
|
-
puts "Switched to task '#{task}'" if @verbose
|
76
|
+
puts "Switched to task '#{task}'" if @config['verbose']
|
80
77
|
|
81
78
|
when 'time','status',nil
|
82
79
|
tasks = get_tasks(args)
|
83
80
|
puts create_log(tasks,'t')
|
84
81
|
|
85
82
|
when 'log'
|
86
|
-
group = args.shift[1] if ['-d','-t'].include?(args[0])
|
83
|
+
group = args.shift[1] if ['-d','-t','-s'].include?(args[0])
|
87
84
|
|
88
85
|
tasks = get_tasks(args)
|
89
86
|
puts create_log(tasks,group)
|
90
87
|
|
91
88
|
when 'clear','delete','del'
|
92
|
-
|
93
|
-
tasks.each do |task|
|
89
|
+
get_tasks(args).each do |task|
|
94
90
|
@trackr.clear(task)
|
95
|
-
puts "Task '#{task}' cleared" if @verbose
|
91
|
+
puts "Task '#{task}' cleared" if @config['verbose']
|
96
92
|
end
|
97
93
|
|
98
94
|
when 'rename','mv'
|
99
95
|
from = args.shift
|
100
96
|
to = args.shift
|
101
97
|
@trackr.rename(from,to)
|
102
|
-
puts "Renamed '#{from}' to '#{to}'" if @verbose
|
98
|
+
puts "Renamed '#{from}' to '#{to}'" if @config['verbose']
|
99
|
+
|
100
|
+
when 'mark','note','n'
|
101
|
+
notes = args.join(' ')
|
102
|
+
@trackr.current.each do |t|
|
103
|
+
@trackr.stop(t)
|
104
|
+
@trackr.start(t, notes)
|
105
|
+
end
|
106
|
+
puts "Annotated task(s) '#{@trackr.current.join(' ')}'" if @config['verbose']
|
103
107
|
|
104
108
|
when 'help'
|
105
109
|
show_help
|
106
110
|
|
111
|
+
when 'config'
|
112
|
+
puts @config.to_yaml
|
113
|
+
|
107
114
|
else
|
108
115
|
puts "'#{cmd}' is not a valid command"
|
109
116
|
show_help
|
@@ -115,10 +122,12 @@ module TimeTrackr
|
|
115
122
|
protected
|
116
123
|
|
117
124
|
def get_tasks(args)
|
125
|
+
# if 'all' add them all to the start
|
118
126
|
if args[0].nil? || args[0] == 'all' || args[0] == '-n'
|
119
127
|
args.unshift(@trackr.tasks).flatten!.delete('all')
|
120
128
|
end
|
121
129
|
|
130
|
+
# any negated tasks?
|
122
131
|
split = args.index('-n') || args.length
|
123
132
|
show = args.slice(0...split).compact.uniq
|
124
133
|
ignore =[*args.slice(split+1..-1)].compact.uniq
|
@@ -136,40 +145,49 @@ module TimeTrackr
|
|
136
145
|
end
|
137
146
|
|
138
147
|
def create_log(tasks,group=nil)
|
139
|
-
|
140
|
-
|
148
|
+
unless group.nil?
|
149
|
+
totals = Hash.new(0)
|
150
|
+
days = {}
|
151
|
+
end
|
141
152
|
table = []
|
142
|
-
# get all periods for selected tasks
|
143
|
-
periods = tasks.each.collect{ |t| @trackr.history(t) }.flatten
|
144
153
|
lastday = nil
|
145
|
-
|
154
|
+
|
155
|
+
# get all periods for selected tasks
|
156
|
+
tasks.each.collect{ |t| @trackr.history(t) }.flatten.sort{ |x,y| x.start <=> y.start }.collect do |period|
|
146
157
|
currday = period.start.strftime(@config['absolute_day'])
|
147
158
|
if currday == lastday
|
148
159
|
day = ''
|
149
160
|
else
|
150
161
|
day = currday
|
151
|
-
days[day] = Hash.new(0)
|
162
|
+
days[day] = Hash.new(0) unless group.nil?
|
152
163
|
end
|
153
164
|
lastday = currday
|
154
165
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
166
|
+
if group.nil?
|
167
|
+
# full log
|
168
|
+
name = period.current? ? period.task+' *' : period.task
|
169
|
+
start = period.start.strftime(@config['absolute_time'])
|
170
|
+
stop = period.current? ? ' ' : period.stop.strftime(@config['absolute_time'])
|
171
|
+
length = format_time(period.length, @config['relative_format'])
|
172
|
+
notes = period.notes
|
173
|
+
table << "#{day.ljust(12)} #{name.ljust(15)} #{start} - #{stop.ljust(5)} #{length} #{notes}"
|
174
|
+
|
175
|
+
elsif group == 's'
|
176
|
+
days[currday]['summary'] = days[currday]['summary'] + period.length
|
177
|
+
else
|
178
|
+
totals[period.task] = totals[period.task] + period.length
|
179
|
+
days[currday][period.task] = days[currday][period.task] + period.length
|
180
|
+
end
|
165
181
|
end
|
166
182
|
|
183
|
+
# build the table for groupings
|
167
184
|
case group
|
168
185
|
when 't'
|
169
186
|
tasks.each do |task|
|
170
187
|
name = @trackr.current.include?(task) ? task+' *' : task
|
171
188
|
table << name.ljust(15) + format_time(totals[task],@config['relative_format'])
|
172
189
|
end
|
190
|
+
|
173
191
|
when 'd'
|
174
192
|
prev_date = ''
|
175
193
|
days.each_pair do |date,tasks|
|
@@ -180,7 +198,14 @@ module TimeTrackr
|
|
180
198
|
table << "#{date_string.ljust(12)} #{name.ljust(15)} " + format_time(length, @config['relative_format'])
|
181
199
|
end
|
182
200
|
end
|
201
|
+
|
202
|
+
when 's'
|
203
|
+
days.each_pair do |date,day|
|
204
|
+
table << "#{date.ljust(12)} " + format_time(day['summary'], @config['relative_format'])
|
205
|
+
end
|
183
206
|
end
|
207
|
+
|
208
|
+
# spit it out
|
184
209
|
table
|
185
210
|
end
|
186
211
|
|
@@ -193,11 +218,17 @@ module TimeTrackr
|
|
193
218
|
|
194
219
|
Available commands:
|
195
220
|
|
196
|
-
start
|
197
|
-
stop
|
198
|
-
switch TASK
|
199
|
-
|
200
|
-
|
221
|
+
start TASK start a task
|
222
|
+
stop [TASK] stop a task (or 'all')
|
223
|
+
switch TASK switch tasks
|
224
|
+
log [TASK] show time log for a task (or 'all')
|
225
|
+
-d group by day
|
226
|
+
-t group by task
|
227
|
+
-s summary (by day, no tasks)
|
228
|
+
time [TASK] same as 'log -t'
|
229
|
+
mark NOTES add notes to all current tasks
|
230
|
+
rename OLD NEW rename a task
|
231
|
+
config current config
|
201
232
|
|
202
233
|
Global options
|
203
234
|
-h --help show this help
|
data/lib/timetrackr/database.rb
CHANGED
@@ -1,103 +1,109 @@
|
|
1
|
+
#
|
2
|
+
# keeps the following format in a yaml file:
|
3
|
+
#
|
4
|
+
# :current: []
|
5
|
+
#
|
6
|
+
# :tasks:
|
7
|
+
# foo:
|
8
|
+
# - :start: 2011-05-16 14:26:26.263449 +07:00
|
9
|
+
# :stop: 2011-05-16 14:26:27 +07:00
|
10
|
+
# :notes: "blah blah blah"
|
11
|
+
#
|
1
12
|
|
2
13
|
module TimeTrackr
|
3
14
|
class Database
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
puts 'YAML not found'
|
15
|
+
def initialize(options={})
|
16
|
+
@options = options
|
17
|
+
begin
|
18
|
+
require 'yaml'
|
19
|
+
@log_path = options['path']
|
20
|
+
if !File.exist? @log_path
|
21
|
+
@db = {:current => [], :tasks => {}}
|
22
|
+
write_file
|
13
23
|
end
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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}"
|
24
|
+
@db = YAML.load_file(@log_path)
|
25
|
+
puts 'Loaded database' if options['verbose']
|
26
|
+
rescue LoadError
|
27
|
+
puts 'YAML not found'
|
35
28
|
end
|
36
|
-
db
|
37
29
|
end
|
38
30
|
|
39
31
|
#
|
40
32
|
# return an array of current tasks
|
41
33
|
#
|
42
34
|
def current
|
43
|
-
|
35
|
+
@db[:current]
|
44
36
|
end
|
45
37
|
|
46
38
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
def start(task,notes)
|
50
|
-
raise 'Not implemented'
|
51
|
-
end
|
52
|
-
|
53
|
-
#
|
54
|
-
# stop a period
|
39
|
+
# return an array of all tasks
|
55
40
|
#
|
56
|
-
def
|
57
|
-
|
41
|
+
def tasks
|
42
|
+
@db[:tasks].keys.compact.uniq || []
|
58
43
|
end
|
59
44
|
|
60
45
|
#
|
61
|
-
#
|
46
|
+
# start a period with optional notes
|
62
47
|
#
|
63
|
-
def
|
64
|
-
|
48
|
+
def start(task, notes)
|
49
|
+
@db[:tasks][task] = Array[] unless @db[:tasks][task]
|
50
|
+
if !@db[:current].include?(task)
|
51
|
+
@db[:current].unshift(task)
|
52
|
+
@db[:tasks][task].push({:start => Time.now, :notes => notes})
|
53
|
+
end
|
65
54
|
end
|
66
55
|
|
67
56
|
#
|
68
|
-
#
|
57
|
+
# stop a period
|
69
58
|
#
|
70
|
-
def
|
71
|
-
|
59
|
+
def stop(task)
|
60
|
+
if @db[:current].include?(task)
|
61
|
+
@db[:current].delete(task)
|
62
|
+
@db[:tasks][task].last[:stop] = Time.now
|
63
|
+
end
|
72
64
|
end
|
73
65
|
|
74
66
|
#
|
75
67
|
# get task history as an array of Periods
|
76
68
|
#
|
77
|
-
def history(task)
|
78
|
-
|
69
|
+
def history(task, p_begin=nil, p_end=nil)
|
70
|
+
@db[:tasks][task].sort{|x,y| x[:start] <=> y[:start]}.collect {|p|
|
71
|
+
Period.new(task,p[:start],p[:stop],p[:notes])
|
72
|
+
} unless !@db[:tasks].include? task
|
79
73
|
end
|
80
74
|
|
81
75
|
#
|
82
76
|
# rename a task
|
83
77
|
#
|
84
78
|
def rename(from, to)
|
85
|
-
|
79
|
+
@db[:tasks][to] = @db[:tasks].delete(from)
|
80
|
+
if @db[:current].delete(from)
|
81
|
+
@db[:current].unshift(to)
|
82
|
+
end
|
86
83
|
end
|
87
84
|
|
88
85
|
#
|
89
|
-
#
|
86
|
+
# cleanup and close
|
90
87
|
#
|
91
|
-
def
|
92
|
-
|
88
|
+
def close
|
89
|
+
write_file
|
93
90
|
end
|
94
91
|
|
95
92
|
#
|
96
|
-
#
|
93
|
+
# clear an task
|
97
94
|
#
|
98
|
-
def
|
95
|
+
def clear(task)
|
96
|
+
@db[:current].delete(task)
|
97
|
+
@db[:tasks].delete(task)
|
99
98
|
end
|
100
99
|
|
100
|
+
private
|
101
|
+
|
102
|
+
def write_file
|
103
|
+
File.open(@log_path,'w') do |fh|
|
104
|
+
YAML.dump(@db,fh)
|
105
|
+
puts 'Saved database' if @options['verbose']
|
106
|
+
end
|
107
|
+
end
|
101
108
|
end
|
102
109
|
end
|
103
|
-
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: timetrackr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Felix Hanley
|
@@ -10,109 +10,40 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
name: sqlite3
|
18
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
-
none: false
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: "0"
|
24
|
-
type: :development
|
25
|
-
prerelease: false
|
26
|
-
version_requirements: *id001
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: json
|
29
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
-
none: false
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: "0"
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: *id002
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: shoulda
|
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
|
52
|
-
none: false
|
53
|
-
requirements:
|
54
|
-
- - ~>
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: 1.0.0
|
57
|
-
type: :development
|
58
|
-
prerelease: false
|
59
|
-
version_requirements: *id004
|
60
|
-
- !ruby/object:Gem::Dependency
|
61
|
-
name: jeweler
|
62
|
-
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
-
none: false
|
64
|
-
requirements:
|
65
|
-
- - ~>
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: 1.5.2
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: *id005
|
71
|
-
- !ruby/object:Gem::Dependency
|
72
|
-
name: rcov
|
73
|
-
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
|
-
requirements:
|
76
|
-
- - ">="
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: "0"
|
79
|
-
type: :development
|
80
|
-
prerelease: false
|
81
|
-
version_requirements: *id006
|
82
|
-
description: A simple time tracking utility
|
13
|
+
date: 2011-10-28 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
83
17
|
email: felix@seconddrawer.com.au
|
84
18
|
executables:
|
85
19
|
- timetrackr
|
86
20
|
extensions: []
|
87
21
|
|
88
22
|
extra_rdoc_files:
|
89
|
-
-
|
90
|
-
- README.mkd
|
23
|
+
- README
|
91
24
|
files:
|
25
|
+
- Rakefile
|
92
26
|
- Gemfile
|
93
27
|
- Gemfile.lock
|
94
|
-
- LICENSE
|
95
|
-
- README
|
96
|
-
-
|
97
|
-
-
|
28
|
+
- LICENSE
|
29
|
+
- README
|
30
|
+
- CHANGELOG
|
31
|
+
- Manifest
|
32
|
+
- TODO
|
98
33
|
- bin/timetrackr
|
99
|
-
- lib/timetrackr.rb
|
100
|
-
- lib/timetrackr/cli.rb
|
101
|
-
- lib/timetrackr/database.rb
|
102
|
-
- lib/timetrackr/json.rb
|
103
|
-
- lib/timetrackr/period.rb
|
104
|
-
- lib/timetrackr/sqlite.rb
|
105
|
-
- lib/timetrackr/yaml.rb
|
106
34
|
- test/helper.rb
|
107
35
|
- test/test_timetrackr.rb
|
108
|
-
- timetrackr.
|
109
|
-
|
36
|
+
- lib/timetrackr/period.rb
|
37
|
+
- lib/timetrackr/database.rb
|
38
|
+
- lib/timetrackr/cli.rb
|
39
|
+
- lib/timetrackr.rb
|
110
40
|
homepage: http://github.com/felix/timetrackr
|
111
|
-
licenses:
|
112
|
-
- MIT
|
113
|
-
post_install_message:
|
114
|
-
rdoc_options: []
|
41
|
+
licenses: []
|
115
42
|
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --main
|
46
|
+
- README
|
116
47
|
require_paths:
|
117
48
|
- lib
|
118
49
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -120,9 +51,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
51
|
requirements:
|
121
52
|
- - ">="
|
122
53
|
- !ruby/object:Gem::Version
|
123
|
-
hash: -4452594232215196430
|
124
|
-
segments:
|
125
|
-
- 0
|
126
54
|
version: "0"
|
127
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
56
|
none: false
|
@@ -133,10 +61,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
61
|
requirements: []
|
134
62
|
|
135
63
|
rubyforge_project:
|
136
|
-
rubygems_version: 1.
|
64
|
+
rubygems_version: 1.8.5
|
137
65
|
signing_key:
|
138
66
|
specification_version: 3
|
139
67
|
summary: A simple time tracking utility
|
140
|
-
test_files:
|
141
|
-
|
142
|
-
- test/test_timetrackr.rb
|
68
|
+
test_files: []
|
69
|
+
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.1.6
|
data/lib/timetrackr/json.rb
DELETED
@@ -1,68 +0,0 @@
|
|
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/sqlite.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
module TimeTrackr
|
2
|
-
class SqliteDatabase < TimeTrackr::Database
|
3
|
-
|
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 (
|
9
|
-
id INTEGER PRIMARY KEY,
|
10
|
-
task TEXT,
|
11
|
-
start TIME,
|
12
|
-
stop TIME,
|
13
|
-
notes TEXT);"
|
14
|
-
@db.execute(sql_events)
|
15
|
-
else
|
16
|
-
@db = SQLite3::Database.open(@log_path)
|
17
|
-
end
|
18
|
-
@db.type_translation = true
|
19
|
-
end
|
20
|
-
|
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
|
-
|
28
|
-
def tasks
|
29
|
-
sql = "SELECT DISTINCT task FROM events;"
|
30
|
-
@db.execute(sql).collect{ |row|
|
31
|
-
row.first
|
32
|
-
}
|
33
|
-
end
|
34
|
-
|
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
|
42
|
-
end
|
43
|
-
|
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
|
51
|
-
end
|
52
|
-
|
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
|
-
|
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
|
-
|
65
|
-
def clear(task)
|
66
|
-
sql = "DELETE FROM events WHERE task = :task;"
|
67
|
-
@db.execute(sql, 'task' => task)
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
end
|
data/lib/timetrackr/yaml.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# keeps the following format in a yaml file:
|
3
|
-
#
|
4
|
-
# :current: []
|
5
|
-
#
|
6
|
-
# :tasks:
|
7
|
-
# foo:
|
8
|
-
# - :start: 2011-05-16 14:26:26.263449 +07:00
|
9
|
-
# :stop: 2011-05-16 14:26:27 +07:00
|
10
|
-
# :notes: "blah blah blah"
|
11
|
-
#
|
12
|
-
|
13
|
-
module TimeTrackr
|
14
|
-
class YamlDatabase < TimeTrackr::Database
|
15
|
-
|
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)
|
23
|
-
end
|
24
|
-
|
25
|
-
def current
|
26
|
-
@db[:current]
|
27
|
-
end
|
28
|
-
|
29
|
-
def tasks
|
30
|
-
@db[:tasks].keys.compact.uniq || []
|
31
|
-
end
|
32
|
-
|
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
|
39
|
-
end
|
40
|
-
|
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
|
46
|
-
end
|
47
|
-
|
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
|
-
|
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
|
59
|
-
end
|
60
|
-
|
61
|
-
def close
|
62
|
-
write_file
|
63
|
-
end
|
64
|
-
|
65
|
-
def clear(task)
|
66
|
-
@db[:current].delete(task)
|
67
|
-
@db[:tasks].delete(task)
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def write_file
|
73
|
-
File.open(@log_path,'w') do |fh|
|
74
|
-
YAML.dump(@db,fh)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
data/timetrackr.gemspec
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = %q{timetrackr}
|
8
|
-
s.version = "0.1.6"
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Felix Hanley"]
|
12
|
-
s.date = %q{2011-06-02}
|
13
|
-
s.default_executable = %q{timetrackr}
|
14
|
-
s.description = %q{A simple time tracking utility}
|
15
|
-
s.email = %q{felix@seconddrawer.com.au}
|
16
|
-
s.executables = ["timetrackr"]
|
17
|
-
s.extra_rdoc_files = [
|
18
|
-
"LICENSE.txt",
|
19
|
-
"README.mkd"
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
"Gemfile",
|
23
|
-
"Gemfile.lock",
|
24
|
-
"LICENSE.txt",
|
25
|
-
"README.mkd",
|
26
|
-
"Rakefile",
|
27
|
-
"VERSION",
|
28
|
-
"bin/timetrackr",
|
29
|
-
"lib/timetrackr.rb",
|
30
|
-
"lib/timetrackr/cli.rb",
|
31
|
-
"lib/timetrackr/database.rb",
|
32
|
-
"lib/timetrackr/json.rb",
|
33
|
-
"lib/timetrackr/period.rb",
|
34
|
-
"lib/timetrackr/sqlite.rb",
|
35
|
-
"lib/timetrackr/yaml.rb",
|
36
|
-
"test/helper.rb",
|
37
|
-
"test/test_timetrackr.rb",
|
38
|
-
"timetrackr.gemspec"
|
39
|
-
]
|
40
|
-
s.homepage = %q{http://github.com/felix/timetrackr}
|
41
|
-
s.licenses = ["MIT"]
|
42
|
-
s.require_paths = ["lib"]
|
43
|
-
s.rubygems_version = %q{1.6.2}
|
44
|
-
s.summary = %q{A simple time tracking utility}
|
45
|
-
s.test_files = [
|
46
|
-
"test/helper.rb",
|
47
|
-
"test/test_timetrackr.rb"
|
48
|
-
]
|
49
|
-
|
50
|
-
if s.respond_to? :specification_version then
|
51
|
-
s.specification_version = 3
|
52
|
-
|
53
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
54
|
-
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
55
|
-
s.add_development_dependency(%q<json>, [">= 0"])
|
56
|
-
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
57
|
-
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
58
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
59
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
60
|
-
else
|
61
|
-
s.add_dependency(%q<sqlite3>, [">= 0"])
|
62
|
-
s.add_dependency(%q<json>, [">= 0"])
|
63
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
64
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
65
|
-
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
66
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
67
|
-
end
|
68
|
-
else
|
69
|
-
s.add_dependency(%q<sqlite3>, [">= 0"])
|
70
|
-
s.add_dependency(%q<json>, [">= 0"])
|
71
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
72
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
73
|
-
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
74
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|