timetrackr 0.1.6 → 0.2.0
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/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
|
-
|