tempo-cli 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/features/start.feature +9 -10
- data/lib/tempo/exceptions.rb +39 -0
- data/lib/tempo/models/log.rb +5 -0
- data/lib/tempo/models/time_record.rb +153 -48
- data/lib/tempo/version.rb +1 -1
- data/lib/tempo/views/view_records/base.rb +0 -3
- data/lib/tempo.rb +1 -0
- data/tempo-cli.gemspec +0 -2
- data/test/lib/tempo/exceptions_test.rb +31 -0
- data/test/lib/tempo/models/log_test.rb +1 -1
- data/test/lib/tempo/models/time_record_test.rb +89 -11
- data/test/lib/tempo/views/reporter_test.rb +1 -1
- metadata +4 -2
data/Gemfile.lock
CHANGED
data/features/start.feature
CHANGED
@@ -36,6 +36,7 @@ Feature: Start Command starts a new time record
|
|
36
36
|
When I run `tempo start --end "1 hour from now"`
|
37
37
|
Then the stdout should contain "time record started"
|
38
38
|
And the output should match /\d{2}:\d{2} - \d{2}:\d{2}/
|
39
|
+
|
39
40
|
@pending
|
40
41
|
Scenario: Adding a time record with tags
|
41
42
|
Given an existing project file
|
@@ -45,7 +46,7 @@ Feature: Start Command starts a new time record
|
|
45
46
|
When I run `tempo start --at "1-1-2014 7:00"`
|
46
47
|
And I run `tempo end --at "1-1-2014 10:00"`
|
47
48
|
And I run `tempo start --at "1-1-2014 8:00"`
|
48
|
-
Then the stderr should contain "error:
|
49
|
+
Then the stderr should contain "error: time <08:00> conflicts with existing record: 07:00 - 10:00"
|
49
50
|
|
50
51
|
Scenario: Adding a time record and closing out the last one
|
51
52
|
Given an existing project file
|
@@ -60,21 +61,15 @@ Feature: Start Command starts a new time record
|
|
60
61
|
And I run `tempo start --at "1-3-2014 10:00"`
|
61
62
|
Then the stdout should contain "time record started"
|
62
63
|
And the time record 20140101 should contain ":end_time: 2014-01-01 23:59" at line 5
|
63
|
-
@pending
|
64
|
-
Scenario: Adding an evening time record should compensate for local time
|
65
|
-
# need to mock entering time in the evening, 21:26:46 -0400
|
66
|
-
# make sure --at "5:00" is recorded for the local day, not GMC
|
67
64
|
|
68
|
-
|
69
|
-
# check on and fix this bug.
|
70
|
-
@pending
|
65
|
+
|
71
66
|
Scenario: Adding an earlier time record should immediately close out
|
72
67
|
Given an existing project file
|
73
|
-
When I run `tempo start --at "1-1-2014
|
68
|
+
When I run `tempo start --at "1-1-2014 10:00"`
|
74
69
|
And I run `tempo start --at "1-1-2014 17:00"`
|
75
70
|
And I run `tempo start --at "1-1-2014 6:00"`
|
76
71
|
Then the stdout should contain "time record started"
|
77
|
-
And the time record 20140101 should contain ":end_time: 2014-01-01
|
72
|
+
And the time record 20140101 should contain ":end_time: 2014-01-01 10:00" at line 5
|
78
73
|
|
79
74
|
Scenario: Adding an earlier day time record should immediately close out
|
80
75
|
Given an existing project file
|
@@ -83,5 +78,9 @@ Feature: Start Command starts a new time record
|
|
83
78
|
Then the stdout should contain "time record started"
|
84
79
|
And the time record 20140101 should contain ":end_time: 2014-01-01 23:59" at line 5
|
85
80
|
|
81
|
+
@pending
|
82
|
+
Scenario: Adding an evening time record should compensate for local time
|
83
|
+
# need to mock entering time in the evening, 21:26:46 -0400
|
84
|
+
# make sure --at "5:00" is recorded for the local day, not GMC
|
86
85
|
|
87
86
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Tempo
|
2
|
+
|
3
|
+
# Reporter raises and error when view records of unknown format
|
4
|
+
class InvalidViewRecordError < Exception
|
5
|
+
end
|
6
|
+
|
7
|
+
# This error is raised when an existing time period conflicts with a time or time period
|
8
|
+
# All parameters are optional, and will be build into the error string if supplied
|
9
|
+
#
|
10
|
+
# Expected parameters are Time objects, but nil or strings will be handled as well
|
11
|
+
#
|
12
|
+
# examples:
|
13
|
+
#
|
14
|
+
# () => "time conflicts with existing record"
|
15
|
+
# (<8:00>, <10:00>) => "time conflicts with existing record: 8:00 - 10:00"
|
16
|
+
# (<8:00>, <10:00>, <9:00>) => "time <9:00> conflicts with existing record: 8:00 - 10:00"
|
17
|
+
# (<8:00>, <10:00>, <9:00>, <9:30>) => "time <9:00 - 9:30> conflicts with existing record: 8:00 - 10:00"
|
18
|
+
# (<8:00>, :running) => "time conflicts with existing record: 8:00 - running"
|
19
|
+
#
|
20
|
+
class TimeConflictError < ArgumentError
|
21
|
+
|
22
|
+
def initialize( start_time=nil, end_time=nil, target_start_time=nil, target_end_time=nil )
|
23
|
+
|
24
|
+
@end_time = (end_time.kind_of? Time) ? end_time.strftime('%H:%M') : end_time.to_s
|
25
|
+
@end_time = " - #{@end_time}" if !@end_time.empty?
|
26
|
+
@existing = (start_time.kind_of? Time) ? ": #{start_time.strftime('%H:%M')}#{@end_time}" : start_time.to_s
|
27
|
+
|
28
|
+
@target_end_time = (target_end_time.kind_of? Time) ? "#{target_end_time.strftime('%H:%M')}" : target_end_time.to_s
|
29
|
+
@target_end_time = " - #{@target_end_time}" if !@target_end_time.empty?
|
30
|
+
@target = (target_start_time.kind_of? Time) ? "<#{target_start_time.strftime('%H:%M')}#{@target_end_time}> " : ""
|
31
|
+
|
32
|
+
@message = "time #{@target}conflicts with existing record#{@existing}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/tempo/models/log.rb
CHANGED
@@ -11,6 +11,7 @@ module Tempo
|
|
11
11
|
# for example Jan 1, 2013 would be :"130101"
|
12
12
|
# id counter is managed through the private methods
|
13
13
|
# increase_id_counter and next_id below
|
14
|
+
#
|
14
15
|
def id_counter time
|
15
16
|
dsym = date_symbol time
|
16
17
|
@id_counter = {} unless @id_counter.kind_of? Hash
|
@@ -26,6 +27,7 @@ module Tempo
|
|
26
27
|
# all instances are saved in the index inherited from base.
|
27
28
|
# Additionally, the days index organizes all instances into
|
28
29
|
# arrays by day. This is used for saving to file.
|
30
|
+
#
|
29
31
|
def days_index
|
30
32
|
@days_index = {} unless @days_index.kind_of? Hash
|
31
33
|
@days_index
|
@@ -56,6 +58,7 @@ module Tempo
|
|
56
58
|
end
|
57
59
|
|
58
60
|
# load all the records for a single day
|
61
|
+
#
|
59
62
|
def load_day_record time
|
60
63
|
dsym = date_symbol time
|
61
64
|
if not days_index.has_key? dsym
|
@@ -65,6 +68,7 @@ module Tempo
|
|
65
68
|
end
|
66
69
|
|
67
70
|
# load the records for each day from time 1 to time 2
|
71
|
+
#
|
68
72
|
def load_days_records time_1, time_2
|
69
73
|
|
70
74
|
return if time_1.nil? || time_2.nil?
|
@@ -76,6 +80,7 @@ module Tempo
|
|
76
80
|
end
|
77
81
|
|
78
82
|
# load the records for the most recently recorded day
|
83
|
+
#
|
79
84
|
def load_last_day
|
80
85
|
reg = /(\d+)\.yaml/
|
81
86
|
if records.last
|
@@ -1,17 +1,27 @@
|
|
1
|
+
# Time Record is an extension of the Log Model,
|
2
|
+
# it adds and end time, and verifies that no records overlap
|
3
|
+
# in their time periods. Additionally, one, and only on record
|
4
|
+
# can be running at any given time, and it can only be the most
|
5
|
+
# recent record.
|
6
|
+
|
1
7
|
module Tempo
|
2
8
|
module Model
|
9
|
+
|
3
10
|
class TimeRecord < Tempo::Model::Log
|
4
11
|
attr_accessor :project, :description
|
5
12
|
attr_reader :start_time, :end_time, :tags
|
6
13
|
|
7
14
|
class << self
|
8
15
|
|
16
|
+
# Only one record can be running at any given time. This record
|
17
|
+
# is the class current, and has and end time of :running
|
18
|
+
#
|
9
19
|
def current
|
10
20
|
return @current if @current && @current.end_time == :running
|
11
21
|
@current = nil
|
12
22
|
end
|
13
23
|
|
14
|
-
def current=
|
24
|
+
def current=instance
|
15
25
|
if instance.class == self
|
16
26
|
@current = instance
|
17
27
|
else
|
@@ -20,7 +30,7 @@ module Tempo
|
|
20
30
|
end
|
21
31
|
end
|
22
32
|
|
23
|
-
def initialize
|
33
|
+
def initialize options={}
|
24
34
|
|
25
35
|
# declare these first for model organization when sent to YAML
|
26
36
|
@project_title = nil
|
@@ -28,10 +38,11 @@ module Tempo
|
|
28
38
|
@start_time = nil
|
29
39
|
|
30
40
|
# verify both start time and end time before sending to super
|
41
|
+
# super handles start_time, not end time
|
31
42
|
options[:start_time] ||= Time.now
|
32
|
-
verify_start_time options[:start_time]
|
33
43
|
@end_time = options.fetch :end_time, :running
|
34
|
-
|
44
|
+
verify_times options[:start_time], @end_time
|
45
|
+
|
35
46
|
super options
|
36
47
|
|
37
48
|
project = options.fetch :project, Tempo::Model::Project.current
|
@@ -40,67 +51,92 @@ module Tempo
|
|
40
51
|
@tags = []
|
41
52
|
tag options.fetch(:tags, [])
|
42
53
|
|
43
|
-
|
44
|
-
|
45
|
-
if not self.class.current
|
46
|
-
self.class.current = self
|
47
|
-
else
|
48
|
-
|
49
|
-
current = self.class.current
|
50
|
-
|
51
|
-
# more recent entries exist, need to close out immediately
|
52
|
-
if current.start_time > @start_time
|
53
|
-
if current.start_time.day > @start_time.day
|
54
|
-
out = self.class.end_of_day @start_time
|
55
|
-
@end_time = out
|
56
|
-
# TODO add a new record onto the next day
|
57
|
-
else
|
58
|
-
@end_time = current.start_time
|
59
|
-
end
|
60
|
-
|
61
|
-
# close out the last current record
|
62
|
-
else
|
63
|
-
self.class.close_current @start_time
|
64
|
-
self.class.current = self
|
65
|
-
end
|
66
|
-
end
|
54
|
+
leave_only_one_running
|
55
|
+
end
|
67
56
|
|
68
|
-
|
57
|
+
def start_time= time
|
58
|
+
raise ArgumentError if !time.kind_of? Time
|
59
|
+
if @end_time != :running
|
60
|
+
@start_time = time if verify_times time, @end_time
|
69
61
|
else
|
70
|
-
if
|
71
|
-
if self.class.current.start_time < @start_time
|
72
|
-
self.class.close_current @start_time
|
73
|
-
end
|
74
|
-
end
|
62
|
+
@start_time = time if verify_start_time time
|
75
63
|
end
|
76
64
|
end
|
77
65
|
|
78
|
-
|
79
|
-
|
66
|
+
# end time cannot be set to :running, only to a
|
67
|
+
# valid Time object
|
68
|
+
#
|
69
|
+
def end_time= time
|
70
|
+
raise ArgumentError if !time.kind_of? Time
|
71
|
+
@end_time = time if verify_times self.start_time, time
|
80
72
|
end
|
81
73
|
|
82
|
-
|
83
|
-
|
74
|
+
# method for updating both times at once, necessary if it would
|
75
|
+
# cause a conflict to do them individually
|
76
|
+
#
|
77
|
+
def update_times start_time, end_time
|
78
|
+
raise ArgumentError if !start_time.kind_of? Time
|
79
|
+
raise ArgumentError if !end_time.kind_of? Time
|
80
|
+
verify_times start_time, end_time
|
81
|
+
@start_time = start_time
|
82
|
+
@end_time = end_time
|
83
|
+
leave_only_one_running
|
84
84
|
end
|
85
85
|
|
86
|
+
# Public method to access verify start time,
|
87
|
+
# determine if an error will be raised
|
88
|
+
#
|
86
89
|
def valid_start_time? time
|
90
|
+
return false if !time.kind_of? Time
|
87
91
|
begin
|
88
|
-
|
92
|
+
if @end_time != :running
|
93
|
+
verify_times time, @end_time
|
94
|
+
else
|
95
|
+
verify_start_time time
|
96
|
+
end
|
89
97
|
rescue ArgumentError => e
|
90
98
|
return false
|
91
99
|
end
|
92
100
|
true
|
93
101
|
end
|
94
102
|
|
103
|
+
# Public method to access verify end time,
|
104
|
+
# determine if an error will be raised
|
105
|
+
#
|
95
106
|
def valid_end_time? time
|
107
|
+
return false if !time.kind_of? Time
|
96
108
|
begin
|
97
|
-
|
109
|
+
verify_times self.start_time, time
|
98
110
|
rescue ArgumentError => e
|
99
111
|
return false
|
100
112
|
end
|
101
113
|
true
|
102
114
|
end
|
103
115
|
|
116
|
+
# Returns the next record in time from the current record
|
117
|
+
# Remember, only records loaded from files will be available
|
118
|
+
# to compare against, so it is important to use the following
|
119
|
+
# methods defined in Log first to assure accuracy:
|
120
|
+
# * load_day_record
|
121
|
+
# * load_days_records
|
122
|
+
# * load_last_day
|
123
|
+
#
|
124
|
+
# uses start_time if end time is :running
|
125
|
+
#
|
126
|
+
def next_record
|
127
|
+
next_one = nil
|
128
|
+
end_time = ( @end_time.kind_of? Time ) ? @end_time : @start_time
|
129
|
+
self.class.index.each do |record|
|
130
|
+
next if record == self
|
131
|
+
if next_one == nil && record.start_time >= end_time
|
132
|
+
next_one = record
|
133
|
+
elsif record.start_time >= end_time && record.start_time < next_one.start_time
|
134
|
+
next_one = record
|
135
|
+
end
|
136
|
+
end
|
137
|
+
next_one
|
138
|
+
end
|
139
|
+
|
104
140
|
def project_title
|
105
141
|
Project.find_by_id( @project ).title if @project
|
106
142
|
end
|
@@ -124,7 +160,7 @@ module Tempo
|
|
124
160
|
record
|
125
161
|
end
|
126
162
|
|
127
|
-
def tag
|
163
|
+
def tag tags
|
128
164
|
return unless tags and tags.kind_of? Array
|
129
165
|
tags.each do |tag|
|
130
166
|
tag.split.each {|t| @tags << t if ! @tags.include? t }
|
@@ -132,7 +168,7 @@ module Tempo
|
|
132
168
|
@tags.sort!
|
133
169
|
end
|
134
170
|
|
135
|
-
def untag
|
171
|
+
def untag tags
|
136
172
|
return unless tags and tags.kind_of? Array
|
137
173
|
tags.each do |tag|
|
138
174
|
tag.split.each {|t| @tags.delete t }
|
@@ -157,6 +193,64 @@ module Tempo
|
|
157
193
|
end
|
158
194
|
end
|
159
195
|
|
196
|
+
# If the current project end_time is :running, we
|
197
|
+
# need to update the class running record to the most
|
198
|
+
# recent record that is running. We also need to close out
|
199
|
+
# all other records at the start time of the next record,
|
200
|
+
# or the end of the day if it is the last record on that day.
|
201
|
+
#
|
202
|
+
# If the current project has an end_time, then we
|
203
|
+
# close any previous running record.
|
204
|
+
#
|
205
|
+
def leave_only_one_running
|
206
|
+
|
207
|
+
if running?
|
208
|
+
|
209
|
+
nxt_rcrd = next_record
|
210
|
+
|
211
|
+
# Nothing running, no newer records, make this one current
|
212
|
+
if self.class.current.nil? && nxt_rcrd.nil?
|
213
|
+
self.class.current = self
|
214
|
+
|
215
|
+
# This is the newest record, close out the running record
|
216
|
+
elsif self.class.current && nxt_rcrd.nil?
|
217
|
+
self.class.close_current @start_time
|
218
|
+
self.class.current = self
|
219
|
+
|
220
|
+
# newer records exits, close out on the next record start
|
221
|
+
# date, or end of day if next record is on another day.
|
222
|
+
#
|
223
|
+
# ? Do we care about the current record ?
|
224
|
+
else
|
225
|
+
#current = self.class.current
|
226
|
+
|
227
|
+
# # more recent running entries exist, close this record
|
228
|
+
# if current.start_time > @start_time
|
229
|
+
# if current.start_time.day > @start_time.day
|
230
|
+
# out = self.class.end_of_day @start_time
|
231
|
+
# @end_time = out
|
232
|
+
# else
|
233
|
+
# @end_time = current.start_time
|
234
|
+
# end
|
235
|
+
|
236
|
+
|
237
|
+
if nxt_rcrd.start_time.day == @start_time.day
|
238
|
+
@end_time = nxt_rcrd.start_time
|
239
|
+
else
|
240
|
+
@end_time = self.class.end_of_day @start_time
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Not running, but we still need to close out any earlier running timerecords
|
245
|
+
else
|
246
|
+
if self.class.current
|
247
|
+
if self.class.current.start_time < @start_time
|
248
|
+
self.class.close_current @start_time
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
160
254
|
# check a time against all loaded instances, verify that it doesn't
|
161
255
|
# fall in the middle of any closed time records
|
162
256
|
#
|
@@ -172,13 +266,21 @@ module Tempo
|
|
172
266
|
next if record.end_time == :running
|
173
267
|
next if record == self
|
174
268
|
if time < record.end_time
|
175
|
-
raise
|
269
|
+
raise Tempo::TimeConflictError.new( record.start_time, record.end_time, time ) if time_in_record? time, record
|
176
270
|
end
|
177
271
|
end
|
178
272
|
true
|
179
273
|
end
|
180
274
|
|
181
|
-
|
275
|
+
# We never have an end time without a start time
|
276
|
+
# so this is also the equivalent of a verify_end_time method
|
277
|
+
# This method returns true for any valid start time, and an
|
278
|
+
# end time of :running. This condition, (currently only possible from init)
|
279
|
+
# requires a second check to close out all but the most recent time entry.
|
280
|
+
#
|
281
|
+
def verify_times start_time, end_time
|
282
|
+
|
283
|
+
verify_start_time start_time
|
182
284
|
|
183
285
|
# TODO: a better check for :running conditions
|
184
286
|
return true if end_time == :running
|
@@ -195,15 +297,16 @@ module Tempo
|
|
195
297
|
|
196
298
|
self.class.days_index[dsym].each do |record|
|
197
299
|
next if record == self
|
198
|
-
raise
|
300
|
+
raise Tempo::TimeConflictError.new( record.start_time, record.end_time, start_time, end_time ) if time_span_intersects_record? start_time, end_time, record
|
199
301
|
end
|
200
302
|
true
|
201
303
|
end
|
202
304
|
|
203
305
|
# this is used for both start time and end times,
|
204
|
-
# so it will return
|
205
|
-
#
|
206
|
-
#
|
306
|
+
# so it will return false if the time is :running
|
307
|
+
#
|
308
|
+
# It will return true if it is exactly the record start or end time
|
309
|
+
#
|
207
310
|
def time_in_record? time, record
|
208
311
|
return false if record.end_time == :running
|
209
312
|
time >= record.start_time && time <= record.end_time
|
@@ -214,6 +317,7 @@ module Tempo
|
|
214
317
|
# It does not invalidate a time span earlier than the record with a :running end time,
|
215
318
|
# this condition must be accounted for separately.
|
216
319
|
# It assumes a valid start and end time.
|
320
|
+
#
|
217
321
|
def time_span_intersects_record? start_time, end_time, record
|
218
322
|
if record.end_time == :running
|
219
323
|
return true if start_time <= record.start_time && end_time > record.start_time
|
@@ -227,6 +331,7 @@ module Tempo
|
|
227
331
|
end
|
228
332
|
|
229
333
|
# returns the last minute of the day
|
334
|
+
#
|
230
335
|
def self.end_of_day time
|
231
336
|
Time.new(time.year, time.month, time.day, 23, 59)
|
232
337
|
end
|
data/lib/tempo/version.rb
CHANGED
data/lib/tempo.rb
CHANGED
data/tempo-cli.gemspec
CHANGED
@@ -9,8 +9,6 @@ spec = Gem::Specification.new do |s|
|
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.summary = 'A command line time tracker for recording by day and by project'
|
11
11
|
s.description = 'tempo-cli is a command line time tracking application. Record time spent on projects in YAML files, and manage them from the command line.'
|
12
|
-
# Add your other files here if you make them
|
13
|
-
# Add lib files to lib.tempo.rb
|
14
12
|
s.files = `git ls-files`.split("\n")
|
15
13
|
s.require_paths << 'lib'
|
16
14
|
s.has_rdoc = true
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Tempo::TimeConflictError do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@start_time = Time.new(2014, 1, 2, 7, 15)
|
7
|
+
@end_time = Time.new(2104, 1, 2, 9, 15)
|
8
|
+
@target_start_time = Time.new(2104, 1, 2, 8, 15)
|
9
|
+
@target_end_time = Time.new(2104, 1, 2, 8, 45)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "can create a message with a target duration" do
|
13
|
+
@exception = Tempo::TimeConflictError.new @start_time, @end_time, @target_start_time, @target_end_time
|
14
|
+
@exception.to_s.must_equal "time <08:15 - 08:45> conflicts with existing record: 07:15 - 09:15"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can create a message with a target time" do
|
18
|
+
@exception = Tempo::TimeConflictError.new @start_time, @end_time, @target_start_time
|
19
|
+
@exception.to_s.must_equal "time <08:15> conflicts with existing record: 07:15 - 09:15"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can create a message without a target time" do
|
23
|
+
@exception = Tempo::TimeConflictError.new @start_time, @end_time
|
24
|
+
@exception.to_s.must_equal "time conflicts with existing record: 07:15 - 09:15"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can handle running time entries" do
|
28
|
+
@exception = Tempo::TimeConflictError.new @start_time, :running, @target_start_time
|
29
|
+
@exception.to_s.must_equal "time <08:15> conflicts with existing record: 07:15 - running"
|
30
|
+
end
|
31
|
+
end
|
@@ -78,7 +78,7 @@ describe Tempo do
|
|
78
78
|
Tempo::Model::MessageLog.index[0].message.must_equal "day 1 pet the sheep"
|
79
79
|
end
|
80
80
|
|
81
|
-
it "loads
|
81
|
+
it "loads records for most recent and return day" do
|
82
82
|
log_record_factory
|
83
83
|
last_day = Tempo::Model::MessageLog.load_last_day
|
84
84
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "test_helper"
|
2
|
+
require "pry"
|
2
3
|
|
3
4
|
describe Tempo do
|
4
5
|
|
@@ -66,12 +67,22 @@ describe Tempo do
|
|
66
67
|
Tempo::Model::TimeRecord.current.must_equal nil
|
67
68
|
end
|
68
69
|
|
69
|
-
it "has a running
|
70
|
+
it "has a running boolean method" do
|
70
71
|
time_record_factory
|
71
72
|
@record_1.running?.must_equal false
|
72
73
|
@record_6.running?.must_equal true
|
73
74
|
end
|
74
75
|
|
76
|
+
it "has a next record method" do
|
77
|
+
time_record_factory
|
78
|
+
@record_1.next_record.must_equal @record_2
|
79
|
+
@record_2.next_record.must_equal @record_3
|
80
|
+
@record_3.next_record.must_equal @record_4
|
81
|
+
@record_4.next_record.must_equal @record_5
|
82
|
+
@record_5.next_record.must_equal @record_6
|
83
|
+
@record_6.next_record.must_equal nil
|
84
|
+
end
|
85
|
+
|
75
86
|
it "has a duration method returning seconds" do
|
76
87
|
time_record_factory
|
77
88
|
@record_1.duration.must_equal 1800
|
@@ -92,16 +103,38 @@ describe Tempo do
|
|
92
103
|
@record_3.end_time.must_equal Time.new(2014,1,1,23,59)
|
93
104
|
end
|
94
105
|
|
95
|
-
it "closes out new projects if not the most recent" do
|
106
|
+
it "closes out new projects at if not the most recent" do
|
107
|
+
# Verify project closes out at the start time of the next time record,
|
108
|
+
# which is not necessarily the running record
|
96
109
|
project_factory
|
97
110
|
Tempo::Model::TimeRecord.clear_all
|
98
|
-
@
|
99
|
-
|
111
|
+
@record_1 = Tempo::Model::TimeRecord.new({project: @project_2,
|
112
|
+
description: "day 1 drinking coffee, check on the mushrooms",
|
113
|
+
start_time: Time.new(2014, 1, 1, 9, 30 ) })
|
114
|
+
@record_2 = Tempo::Model::TimeRecord.new({project: @project_2,
|
115
|
+
description: "day 1 pet the sheep",
|
116
|
+
start_time: Time.new(2014, 1, 1, 10, 30 ) })
|
117
|
+
@record_3 = Tempo::Model::TimeRecord.new({project: @project_1,
|
118
|
+
description: "make the coffee, check the weather",
|
119
|
+
start_time: Time.new(2014, 1, 1, 7 ) })
|
100
120
|
Tempo::Model::TimeRecord.current.must_equal @record_2
|
101
|
-
@
|
121
|
+
@record_3.end_time.must_equal @record_1.start_time
|
122
|
+
end
|
123
|
+
|
124
|
+
it "on init closes out new projects on the same day" do
|
125
|
+
project_factory
|
126
|
+
Tempo::Model::TimeRecord.clear_all
|
127
|
+
@record_1 = Tempo::Model::TimeRecord.new({project: @project_2,
|
128
|
+
description: "day 1 drinking coffee, check on the mushrooms",
|
129
|
+
start_time: Time.new(2014, 1, 2, 10, 30 ) })
|
130
|
+
@record_2 = Tempo::Model::TimeRecord.new({project: @project_2,
|
131
|
+
description: "day 1 pet the sheep",
|
132
|
+
start_time: Time.new(2014, 1, 1, 10, 30 ) })
|
133
|
+
Tempo::Model::TimeRecord.current.must_equal @record_1
|
134
|
+
@record_2.end_time.must_equal Time.new(2014, 1, 1, 23, 59 )
|
102
135
|
end
|
103
136
|
|
104
|
-
it "closes
|
137
|
+
it "closes earlier running when init with an end time" do
|
105
138
|
project_factory
|
106
139
|
Tempo::Model::TimeRecord.clear_all
|
107
140
|
@record_1 = Tempo::Model::TimeRecord.new({ project: @project_2, description: "day 1 drinking coffee, check on the mushrooms", start_time: Time.new(2014, 1, 1, 7, 30 ) })
|
@@ -112,12 +145,12 @@ describe Tempo do
|
|
112
145
|
|
113
146
|
it "errors when start time inside existing record" do
|
114
147
|
time_record_factory
|
115
|
-
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 12 ) }) }.must_raise
|
148
|
+
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 12 ) }) }.must_raise Tempo::TimeConflictError
|
116
149
|
end
|
117
150
|
|
118
151
|
it "errors when start time same as existing record" do
|
119
152
|
time_record_factory
|
120
|
-
proc { Tempo::Model::TimeRecord.new({ start_time: @record_1.start_time }) }.must_raise
|
153
|
+
proc { Tempo::Model::TimeRecord.new({ start_time: @record_1.start_time }) }.must_raise Tempo::TimeConflictError
|
121
154
|
end
|
122
155
|
|
123
156
|
it "errors when end time is before start time" do
|
@@ -146,19 +179,19 @@ describe Tempo do
|
|
146
179
|
it "errors when end time in existing record" do
|
147
180
|
Tempo::Model::TimeRecord.clear_all
|
148
181
|
r1 = Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 10 ), end_time: Time.new(2014, 1, 1, 12 ) })
|
149
|
-
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 8 ), end_time: Time.new(2014, 1, 1, 11 ) }) }.must_raise
|
182
|
+
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 8 ), end_time: Time.new(2014, 1, 1, 11 ) }) }.must_raise Tempo::TimeConflictError
|
150
183
|
end
|
151
184
|
|
152
185
|
it "errors when record spans an existing record" do
|
153
186
|
Tempo::Model::TimeRecord.clear_all
|
154
187
|
r1 = Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 10 ), end_time: Time.new(2014, 1, 1, 11 ) })
|
155
|
-
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 8 ), end_time: Time.new(2014, 1, 1, 12 ) }) }.must_raise
|
188
|
+
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 8 ), end_time: Time.new(2014, 1, 1, 12 ) }) }.must_raise Tempo::TimeConflictError
|
156
189
|
end
|
157
190
|
|
158
191
|
it "errors when record spans a running record" do
|
159
192
|
Tempo::Model::TimeRecord.clear_all
|
160
193
|
r1 = Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 10 ) })
|
161
|
-
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 8 ), end_time: Time.new(2014, 1, 1, 12 ) }) }.must_raise
|
194
|
+
proc { Tempo::Model::TimeRecord.new({ start_time: Time.new(2014, 1, 1, 8 ), end_time: Time.new(2014, 1, 1, 12 ) }) }.must_raise Tempo::TimeConflictError
|
162
195
|
end
|
163
196
|
|
164
197
|
it "has a valid start time check for existing record" do
|
@@ -173,6 +206,51 @@ describe Tempo do
|
|
173
206
|
@record_2.valid_end_time?(@record_3.end_time).must_equal false
|
174
207
|
end
|
175
208
|
|
209
|
+
it "can validate and update a start time" do
|
210
|
+
project_factory
|
211
|
+
Tempo::Model::TimeRecord.clear_all
|
212
|
+
@record = Tempo::Model::TimeRecord.new({project: @project_1,
|
213
|
+
start_time: Time.new(2014, 1, 1, 8 ),
|
214
|
+
end_time: Time.new(2014, 1, 1, 10 ) })
|
215
|
+
proc { @record.start_time = Time.new(2014, 1, 1, 11 )}.must_raise ArgumentError
|
216
|
+
|
217
|
+
@record.start_time = Time.new(2014, 1, 1, 9 )
|
218
|
+
@record.start_time.must_equal Time.new(2014, 1, 1, 9 )
|
219
|
+
end
|
220
|
+
|
221
|
+
it "can validate and update an end time" do
|
222
|
+
project_factory
|
223
|
+
Tempo::Model::TimeRecord.clear_all
|
224
|
+
@record = Tempo::Model::TimeRecord.new({project: @project_1,
|
225
|
+
start_time: Time.new(2014, 1, 1, 8 ),
|
226
|
+
end_time: Time.new(2014, 1, 1, 10 ) })
|
227
|
+
|
228
|
+
proc { @record.end_time = Time.new(2014, 1, 1, 7 )}.must_raise ArgumentError
|
229
|
+
@record.end_time = Time.new(2014, 1, 1, 11 )
|
230
|
+
@record.end_time.must_equal Time.new(2014, 1, 1, 11 )
|
231
|
+
end
|
232
|
+
|
233
|
+
it "can update a start and end time" do
|
234
|
+
project_factory
|
235
|
+
Tempo::Model::TimeRecord.clear_all
|
236
|
+
@record = Tempo::Model::TimeRecord.new({project: @project_3,
|
237
|
+
start_time: Time.new(2014, 1, 1, 8 ),
|
238
|
+
end_time: Time.new(2014, 1, 1, 10 ) })
|
239
|
+
|
240
|
+
@record.update_times(Time.new(2014, 1, 1, 8 ), Time.new(2014, 1, 1, 10 ))
|
241
|
+
end
|
242
|
+
|
243
|
+
it "closes earlier running when start time is updated" do
|
244
|
+
project_factory
|
245
|
+
Tempo::Model::TimeRecord.clear_all
|
246
|
+
@record_1 = Tempo::Model::TimeRecord.new({ project: @project_2, description: "record 1", start_time: Time.new(2014, 1, 1, 10 ) })
|
247
|
+
@record_2 = Tempo::Model::TimeRecord.new({ project: @project_1, description: "record 2", start_time: Time.new(2014, 1, 1, 7 ), end_time: Time.new(2014, 1, 1, 8 ) })
|
248
|
+
Tempo::Model::TimeRecord.current.must_equal @record_1
|
249
|
+
|
250
|
+
@record_2.update_times(Time.new(2014, 1, 1, 11 ), Time.new(2014, 1, 1, 12 ))
|
251
|
+
Tempo::Model::TimeRecord.current.must_equal nil
|
252
|
+
end
|
253
|
+
|
176
254
|
it "comes with freeze dry for free" do
|
177
255
|
Tempo::Model::TimeRecord.clear_all
|
178
256
|
time_record_factory
|
@@ -19,7 +19,7 @@ describe Tempo do
|
|
19
19
|
|
20
20
|
it "raises and error when view records of unknown format" do
|
21
21
|
record = "an invalid record object"
|
22
|
-
proc { Tempo::Views::Reporter.add_view_record record }.must_raise Tempo::
|
22
|
+
proc { Tempo::Views::Reporter.add_view_record record }.must_raise Tempo::InvalidViewRecordError
|
23
23
|
end
|
24
24
|
|
25
25
|
it "sends the reports to the screen formatter on report" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tempo-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-12-
|
12
|
+
date: 2013-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- lib/tempo/controllers/report_controller.rb
|
162
162
|
- lib/tempo/controllers/start_controller.rb
|
163
163
|
- lib/tempo/controllers/update_controller.rb
|
164
|
+
- lib/tempo/exceptions.rb
|
164
165
|
- lib/tempo/models/base.rb
|
165
166
|
- lib/tempo/models/composite.rb
|
166
167
|
- lib/tempo/models/log.rb
|
@@ -186,6 +187,7 @@ files:
|
|
186
187
|
- test/lib/file_record/record_test.rb
|
187
188
|
- test/lib/tempo/controllers/base_controller_test.rb
|
188
189
|
- test/lib/tempo/controllers/project_controller_test.rb
|
190
|
+
- test/lib/tempo/exceptions_test.rb
|
189
191
|
- test/lib/tempo/models/base_test.rb
|
190
192
|
- test/lib/tempo/models/composite_test.rb
|
191
193
|
- test/lib/tempo/models/log_test.rb
|