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