one_inch_punch 0.2.4 → 0.3.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/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.3.0 2008-04-05
2
+
3
+ * 1 minor enhancement:
4
+ * Can now have 'sub-projects', or projects with names of the form 'parent/child'
5
+ * A parent will be checked in if a child is checked in
6
+ * Punching out of a parent project will punch the real checked-in project out
7
+ * Listing time and getting totals for a parent project will include child time
8
+ * Some small convenience methods probably nobody cares about, so they aren't included in the total number of enchancements above
9
+
1
10
  == 0.2.4 2008-12-17
2
11
 
3
12
  * 1 tiny enhancement:
data/README.txt CHANGED
@@ -12,13 +12,8 @@ One-inch punch: Smaller, more effective
12
12
  == FEATURES/PROBLEMS:
13
13
 
14
14
  * Can load and write .punch.yml data compatibly with Ara's punch gem
15
- * Can punch in and out of projects (including creating a project by punching in)
16
- * Can query project status
17
- * Can delete a project
18
- * Can list project data
19
- * Can give total time for a project
20
- * Can be used command-line
21
- * Command-line output is less ugly
15
+ * Things you may expect from a time-tracking program, like punching in and out and getting time data
16
+ * The ability to be punched in to multiple projects at once, because double-billing is awesome
22
17
  * More, since this is unfinished
23
18
 
24
19
  == SYNOPSIS:
data/bin/punch CHANGED
@@ -66,10 +66,10 @@ commands = {
66
66
  end,
67
67
  'total' => lambda do |project|
68
68
  result = Punch.total(project, OPTIONS.merge(:format => true))
69
- if project
70
- puts result.inspect
71
- else
69
+ if result.is_a?(Hash)
72
70
  puts result.to_yaml
71
+ else
72
+ puts result.inspect
73
73
  end
74
74
  end,
75
75
  'in' => lambda do |project|
data/lib/punch.rb CHANGED
@@ -45,14 +45,19 @@ class Punch
45
45
  project = nil
46
46
  end
47
47
 
48
- return data.keys.inject({}) { |hash, project| hash.merge(project => status(project, options)) } unless project
48
+ return projects.inject({}) { |hash, project| hash.merge(project => status(project, options)) } unless project
49
49
 
50
50
  project_data = data[project]
51
- return nil if !project_data or project_data.empty?
51
+ time_data = (project_data || []).last
52
+
53
+ if time_data
54
+ status = time_data['out'] ? 'out' : 'in'
55
+ end
56
+
57
+ status, time_data = check_child_status(project, status, time_data)
52
58
 
53
- time_data = project_data.last
54
- status = time_data['out'] ? 'out' : 'in'
55
59
  return status unless options[:full]
60
+ return status if status.nil?
56
61
 
57
62
  { :status => status, :time => time_data[status] }
58
63
  end
@@ -81,7 +86,7 @@ class Punch
81
86
  if project
82
87
  return false unless do_out_single(project, options)
83
88
  else
84
- return false unless data.keys.collect { |project| do_out_single(project, options) }.any?
89
+ return false unless projects.collect { |project| do_out_single(project, options) }.any?
85
90
  end
86
91
  true
87
92
  end
@@ -94,10 +99,17 @@ class Punch
94
99
  def list(*args)
95
100
  options = args.last.is_a?(Hash) ? args.pop : {}
96
101
  project = args.first
102
+
97
103
  if project
98
- do_list_single(project, options)
104
+ list_projects = child_projects(project) + [project]
99
105
  else
100
- data.keys.inject({}) { |hash, project| hash.merge(project => do_list_single(project, options)) }
106
+ list_projects = projects
107
+ end
108
+
109
+ if list_projects.length == 1
110
+ do_list_single(list_projects.first, options)
111
+ else
112
+ list_projects.inject({}) { |hash, project| hash.merge(project => do_list_single(project, options)) }
101
113
  end
102
114
  end
103
115
 
@@ -127,6 +139,7 @@ class Punch
127
139
 
128
140
  def do_out_single(project, options)
129
141
  return false if out?(project)
142
+ project = in_child(project) || project
130
143
  time = time_from_options(options)
131
144
  log(project, options[:message], :time => time) if options[:message]
132
145
  log(project, 'punch out', :time => time)
@@ -149,5 +162,29 @@ class Punch
149
162
  def time_from_options(options)
150
163
  options[:time] || options[:at] || Time.now
151
164
  end
165
+
166
+ def projects
167
+ data.keys
168
+ end
169
+
170
+ def child_projects(project)
171
+ projects.select { |proj| proj.match(/^#{Regexp.escape(project)}/) } - [project]
172
+ end
173
+
174
+ def in_child(project)
175
+ child_projects(project).detect { |proj| status(proj) == 'in' }
176
+ end
177
+
178
+ def check_child_status(project, status, time_data)
179
+ if status != 'in'
180
+ in_child = in_child(project)
181
+ if in_child
182
+ status = 'in'
183
+ time_data = data[in_child].last
184
+ end
185
+ end
186
+
187
+ return status, time_data
188
+ end
152
189
  end
153
190
  end
@@ -36,4 +36,13 @@ class Punch
36
36
  def log(message, options = {})
37
37
  self.class.log(project, message, options)
38
38
  end
39
+
40
+ def ==(other)
41
+ project == other.project
42
+ end
43
+
44
+ def child_projects
45
+ Punch.send(:child_projects, project).collect { |proj| Punch.new(proj) }
46
+ end
47
+ alias_method :children, :child_projects
39
48
  end
data/lib/punch/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  class Punch
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 2
5
- TINY = 4
4
+ MINOR = 3
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -64,11 +64,11 @@ describe 'punch command' do
64
64
  run_command('total', @project)
65
65
  end
66
66
 
67
- it 'should output the total as YAML if no project given' do
68
- result = 'total data'
67
+ it 'should output the total as YAML if the total data is a Hash' do
68
+ result = { :some_project => 'total data', :some_other_project => 'other total data' }
69
69
  Punch.stub!(:total).and_return(result)
70
70
  self.should.receive(:puts).with(result.to_yaml)
71
- run_command('total')
71
+ run_command('total', @project)
72
72
  end
73
73
 
74
74
  it 'should not write the data' do
@@ -187,7 +187,7 @@ describe 'punch command' do
187
187
  run_command('status', @project, '--full')
188
188
  end
189
189
 
190
- it 'should output the status as YAML if not project given even if a full option is given' do
190
+ it 'should output the status as YAML if no project given even if a full option is given' do
191
191
  result = 'status data'
192
192
  Punch.stub!(:status).and_return(result)
193
193
  self.should.receive(:puts).with(result.to_yaml)
@@ -381,4 +381,52 @@ describe Punch, 'instance' do
381
381
  @punch.log(@message).should == @log
382
382
  end
383
383
  end
384
+
385
+ describe 'equality' do
386
+ it 'should be equal to another instance for the same project' do
387
+ Punch.new('proj').should == Punch.new('proj')
388
+ end
389
+
390
+ it 'should not be equal to an instance for a different project' do
391
+ Punch.new('proj').should.not == Punch.new('other')
392
+ end
393
+ end
394
+
395
+ it 'should return child projects' do
396
+ @punch.should.respond_to(:child_projects)
397
+ end
398
+
399
+ describe 'returning child projects' do
400
+ before do
401
+ Punch.instance_eval do
402
+ class << self
403
+ public :data, :data=
404
+ end
405
+ end
406
+
407
+ @projects = {}
408
+ @projects['parent'] = 'daddy'
409
+ @projects['child'] = @projects['parent'] + '/sugar'
410
+ @projects['kid'] = @projects['parent'] + '/object'
411
+
412
+ @data = { @projects['parent'] => [], @projects['child'] => [], @projects['kid'] => [] }
413
+ Punch.data = @data
414
+ end
415
+
416
+ it 'should return instances for each child project' do
417
+ children = Punch.new(@projects['parent']).child_projects
418
+ expected = [Punch.new(@projects['child']), Punch.new(@projects['kid'])]
419
+ children.sort_by { |c| c.project }.should == expected.sort_by { |e| e.project }
420
+ end
421
+
422
+ it "should provide 'children' as an alias" do
423
+ children = Punch.new(@projects['parent']).children
424
+ expected = [Punch.new(@projects['child']), Punch.new(@projects['kid'])]
425
+ children.sort_by { |c| c.project }.should == expected.sort_by { |e| e.project }
426
+ end
427
+
428
+ it 'should return an empty array if the project has no child projects' do
429
+ @punch.child_projects.should == []
430
+ end
431
+ end
384
432
  end
data/spec/punch_spec.rb CHANGED
@@ -242,6 +242,115 @@ describe Punch do
242
242
  }
243
243
  end
244
244
  end
245
+
246
+ describe 'handling a sub-project' do
247
+ before do
248
+ @projects['parent'] = 'daddy'
249
+ @projects['child'] = @projects['parent'] + '/sugar'
250
+ end
251
+
252
+ it "should return 'in' for a non-existent parent project if the sub-project is punched in" do
253
+ @data[@projects['child']] = [ { 'in' => @now } ]
254
+ Punch.data = @data
255
+ Punch.status(@projects['parent']).should == 'in'
256
+ end
257
+
258
+ it "should return 'in' for an empty parent project if the sub-project is punched in" do
259
+ @data[@projects['parent']] = []
260
+ @data[@projects['child']] = [ { 'in' => @now } ]
261
+ Punch.data = @data
262
+ Punch.status(@projects['parent']).should == 'in'
263
+ end
264
+
265
+ it "should return 'in' for a punched-out parent project if the sub-project is punched in" do
266
+ @data[@projects['parent']] = [ { 'in' => @now - 13, 'out' => @now - 5 } ]
267
+ @data[@projects['child']] = [ { 'in' => @now } ]
268
+ Punch.data = @data
269
+ Punch.status(@projects['parent']).should == 'in'
270
+ end
271
+
272
+ it "should use the sub-project's punch-in time for the parent project when returning full status" do
273
+ @data[@projects['child']] = [ { 'in' => @now } ]
274
+ Punch.data = @data
275
+ Punch.status(@projects['parent'], :full => true).should == { :status => 'in', :time => @now }
276
+
277
+ @data[@projects['parent']] = []
278
+ Punch.data = @data
279
+ Punch.status(@projects['parent'], :full => true).should == { :status => 'in', :time => @now }
280
+
281
+ @data[@projects['parent']] = [ { 'in' => @now - 13, 'out' => @now - 5 } ]
282
+ Punch.data = @data
283
+ Punch.status(@projects['parent'], :full => true).should == { :status => 'in', :time => @now }
284
+ end
285
+
286
+ it "should return nil for a non-existent parent project if the sub-project does not exist" do
287
+ Punch.status(@projects['parent']).should.be.nil
288
+ end
289
+
290
+ it "should return nil for an empty parent project if the sub-project does not exist" do
291
+ @data[@projects['parent']] = []
292
+ Punch.data = @data
293
+ Punch.status(@projects['parent']).should.be.nil
294
+ end
295
+
296
+ it "should return nil for a non-existent parent project if the sub-project is empty" do
297
+ @data[@projects['child']] = []
298
+ Punch.data = @data
299
+ Punch.status(@projects['parent']).should.be.nil
300
+ end
301
+
302
+ it "should return nil for an empty parent project if the sub-project is empty" do
303
+ @data[@projects['parent']] = []
304
+ @data[@projects['child']] = []
305
+ Punch.data = @data
306
+ Punch.status(@projects['parent']).should.be.nil
307
+ end
308
+
309
+ it "should return nil for the parent project when returning full status" do
310
+ Punch.status(@projects['parent'], :full => true).should.be.nil
311
+
312
+ @data[@projects['parent']] = []
313
+ Punch.data = @data
314
+ Punch.status(@projects['parent'], :full => true).should.be.nil
315
+
316
+ @data.delete(@projects['parent'])
317
+ @data[@projects['child']] = []
318
+ Punch.data = @data
319
+ Punch.status(@projects['parent'], :full => true).should.be.nil
320
+
321
+ @data[@projects['parent']] = []
322
+ @data[@projects['child']] = []
323
+ Punch.data = @data
324
+ Punch.status(@projects['parent'], :full => true).should.be.nil
325
+ end
326
+
327
+ it "should return 'out' for a punched-out parent project if the sub-project does not exist" do
328
+ @data[@projects['parent']] = [ { 'in' => @now - 13, 'out' => @now - 5 } ]
329
+ Punch.data = @data
330
+ Punch.status(@projects['parent']).should == 'out'
331
+ end
332
+
333
+ it "should return 'out' for a punched-out parent project if the sub-project is empty" do
334
+ @data[@projects['parent']] = [ { 'in' => @now - 13, 'out' => @now - 5 } ]
335
+ @data[@projects['child']] = []
336
+ Punch.data = @data
337
+ Punch.status(@projects['parent']).should == 'out'
338
+ end
339
+
340
+ it "should use the parent project's punch-out time for the parent project when returning full status" do
341
+ @data[@projects['parent']] = [ { 'in' => @now - 13, 'out' => @now - 5 } ]
342
+ Punch.data = @data
343
+ Punch.status(@projects['parent'], :full => true).should == { :status => 'out', :time => @now - 5 }
344
+
345
+ @data[@projects['child']] = []
346
+ Punch.data = @data
347
+ Punch.status(@projects['parent'], :full => true).should == { :status => 'out', :time => @now - 5 }
348
+
349
+ @data[@projects['child']] = [ { 'in' => @now - 4, 'out' => @now - 1 } ]
350
+ Punch.data = @data
351
+ Punch.status(@projects['parent'], :full => true).should == { :status => 'out', :time => @now - 5 }
352
+ end
353
+ end
245
354
  end
246
355
 
247
356
  it 'should indicate whether a project is punched out' do
@@ -674,6 +783,45 @@ describe Punch do
674
783
  end
675
784
  end
676
785
  end
786
+
787
+ describe 'handling a sub-project' do
788
+ before do
789
+ @projects = {}
790
+ @projects['parent'] = 'daddy'
791
+ @projects['child'] = @projects['parent'] + '/sugar'
792
+ end
793
+
794
+ it 'should actually punch out the sub-project when told to punch out the parent project' do
795
+ @data[@projects['parent']] = [ { 'in' => @now - 100, 'out' => @now - 50 } ]
796
+ @data[@projects['child']] = [ { 'in' => @now - 20 } ]
797
+ Punch.data = @data
798
+ Punch.out(@projects['parent'])
799
+ Punch.data[@projects['child']].last['out'].should == @now
800
+ end
801
+
802
+ it 'should not change the punch-out time for the parent project' do
803
+ @data[@projects['parent']] = [ { 'in' => @now - 100, 'out' => @now - 50 } ]
804
+ @data[@projects['child']] = [ { 'in' => @now - 20 } ]
805
+ Punch.data = @data
806
+ Punch.out(@projects['parent'])
807
+ Punch.data[@projects['parent']].last['out'].should == @now - 50
808
+ end
809
+
810
+ it 'should not add data for a non-existent parent project' do
811
+ @data[@projects['child']] = [ { 'in' => @now - 20 } ]
812
+ Punch.data = @data
813
+ Punch.out(@projects['parent'])
814
+ Punch.data[@projects['parent']].should.be.nil
815
+ end
816
+
817
+ it 'should not add data for an empty parent project' do
818
+ @data[@projects['parent']] = []
819
+ @data[@projects['child']] = [ { 'in' => @now - 20 } ]
820
+ Punch.data = @data
821
+ Punch.out(@projects['parent'])
822
+ Punch.data[@projects['parent']].should == []
823
+ end
824
+ end
677
825
  end
678
826
 
679
827
  it 'should delete a project' do
@@ -830,6 +978,41 @@ describe Punch do
830
978
  Punch.data.should == old_data
831
979
  end
832
980
  end
981
+
982
+ describe 'handling a sub-project' do
983
+ before do
984
+ @projects = {}
985
+ @projects['parent'] = 'daddy'
986
+ @projects['child'] = @projects['parent'] + '/sugar'
987
+ @data[@projects['parent']] = [ { 'in' => @now - 100, 'out' => @now - 50 } ]
988
+ @data[@projects['child']] = [ { 'in' => @now - 20 } ]
989
+ Punch.data = @data
990
+ end
991
+
992
+ it 'should return data for the parent and sub-project' do
993
+ list_data = { @projects['parent'] => @data[@projects['parent']], @projects['child'] => @data[@projects['child']] }
994
+ Punch.list(@projects['parent']).should == list_data
995
+ end
996
+
997
+ it 'should respect options' do
998
+ list_data = { @projects['parent'] => [], @projects['child'] => @data[@projects['child']] }
999
+ Punch.list(@projects['parent'], :after => @now - 21).should == list_data
1000
+ end
1001
+
1002
+ describe 'when do project is given' do
1003
+ before do
1004
+ @projects = ['test project', 'out project', 'other project']
1005
+ @data[@projects[0]] = [ {'in' => @now - 50, 'out' => @now - 25} ]
1006
+ @data[@projects[1]] = [ {'in' => @now - 300, 'out' => @now - 250}, {'in' => @now - 40, 'out' => @now - 20} ]
1007
+ @data[@projects[2]] = [ {'in' => @now - 50, 'out' => @now - 35} ]
1008
+ Punch.data = @data
1009
+ end
1010
+
1011
+ it 'should return data for all projects' do
1012
+ Punch.list.should == @data
1013
+ end
1014
+ end
1015
+ end
833
1016
  end
834
1017
 
835
1018
  it 'should get the total time for a project' do
@@ -930,15 +1113,51 @@ describe Punch do
930
1113
  end
931
1114
 
932
1115
  it 'should give totals for all projects' do
933
- Punch.total.should == { @projects[0] => 25, @projects[1] => 70, @projects[2] => 15}
1116
+ Punch.total.should == { @projects[0] => 25, @projects[1] => 70, @projects[2] => 15 }
934
1117
  end
935
1118
 
936
1119
  it 'should respect options' do
937
- Punch.total(:after => @now - 51).should == { @projects[0] => 25, @projects[1] => 20, @projects[2] => 15}
1120
+ Punch.total(:after => @now - 51).should == { @projects[0] => 25, @projects[1] => 20, @projects[2] => 15 }
938
1121
  end
939
1122
 
940
1123
  it 'should format the time spent if passed a format option' do
941
- Punch.total(:format => true).should == { @projects[0] => "00:25", @projects[1] => "01:10", @projects[2] => "00:15"}
1124
+ Punch.total(:format => true).should == { @projects[0] => "00:25", @projects[1] => "01:10", @projects[2] => "00:15" }
1125
+ end
1126
+ end
1127
+
1128
+ describe 'handling a sub-project' do
1129
+ before do
1130
+ @projects = {}
1131
+ @projects['parent'] = 'daddy'
1132
+ @projects['child'] = @projects['parent'] + '/sugar'
1133
+ @data[@projects['parent']] = [ { 'in' => @now - 100, 'out' => @now - 50 } ]
1134
+ @data[@projects['child']] = [ { 'in' => @now - 20, 'out' => @now - 10 } ]
1135
+ Punch.data = @data
1136
+ end
1137
+
1138
+ it 'should return data for the parent and sub-project' do
1139
+ total_data = { @projects['parent'] => 50, @projects['child'] => 10 }
1140
+ Punch.total(@projects['parent']).should == total_data
1141
+ end
1142
+
1143
+ it 'should respect options' do
1144
+ total_data = { @projects['parent'] => 0, @projects['child'] => 10 }
1145
+ Punch.total(@projects['parent'], :after => @now - 21).should == total_data
1146
+ end
1147
+
1148
+ describe 'when do project is given' do
1149
+ before do
1150
+ @extra_projects = ['test project', 'out project', 'other project']
1151
+ @data[@extra_projects[0]] = [ {'in' => @now - 50, 'out' => @now - 25} ]
1152
+ @data[@extra_projects[1]] = [ {'in' => @now - 300, 'out' => @now - 250}, {'in' => @now - 40, 'out' => @now - 20} ]
1153
+ @data[@extra_projects[2]] = [ {'in' => @now - 50, 'out' => @now - 35} ]
1154
+ Punch.data = @data
1155
+ end
1156
+
1157
+ it 'should give totals for all projects' do
1158
+ total_data = { @extra_projects[0] => 25, @extra_projects[1] => 70, @extra_projects[2] => 15, @projects['parent'] => 50, @projects['child'] => 10 }
1159
+ Punch.total.should == total_data
1160
+ end
942
1161
  end
943
1162
  end
944
1163
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: one_inch_punch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yossef Mendelssohn
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-17 00:00:00 -06:00
12
+ date: 2009-04-05 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency