one_inch_punch 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,18 @@
1
+ == 0.0.3 2008-08-25
2
+
3
+ * 5 enhancements:
4
+ * Can now get total time for a project
5
+ * Listing and getting total time accept arguments of :before and :after
6
+ * Can log messages while punched in for a project
7
+ * Punching in and out automatically create log messages
8
+ * Some operations can be performed on all projects at once:
9
+ * status
10
+ * out
11
+ * list
12
+ * total
13
+ * 1 change:
14
+ * Data-changing operations no longer automatically write the data
15
+
1
16
  == 0.0.2 2008-08-22
2
17
 
3
18
  * 3 tiny enhancements:
data/README.txt CHANGED
@@ -16,8 +16,8 @@ One-inch punch: Smaller, more effective
16
16
  * Can query project status
17
17
  * Can delete a project
18
18
  * Can list project data
19
+ * Can give total time for a project
19
20
 
20
- * Cannot give a total time (yet)
21
21
  * Cannot be used command-line (yet)
22
22
  * More, since this is unfinished
23
23
 
@@ -32,6 +32,7 @@ One-inch punch: Smaller, more effective
32
32
  # do some work
33
33
  Punch.out('my project')
34
34
  Punch.out?('my project') # => true
35
+ Punch.write
35
36
 
36
37
  == REQUIREMENTS:
37
38
 
data/lib/punch/version.rb CHANGED
@@ -2,7 +2,7 @@ module Punch
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 2
5
+ TINY = 3
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/lib/punch.rb CHANGED
@@ -31,16 +31,14 @@ module Punch
31
31
  end
32
32
  end
33
33
 
34
- def status(project)
34
+ def status(project = nil)
35
+ return data.keys.inject({}) { |hash, project| hash.merge(project => status(project)) } unless project
36
+
35
37
  project_data = data[project]
36
38
  return nil if !project_data or project_data.empty?
37
39
 
38
40
  time_data = project_data.last
39
- if time_data['out']
40
- 'out'
41
- else
42
- 'in'
43
- end
41
+ time_data['out'] ? 'out' : 'in'
44
42
  end
45
43
 
46
44
  def out?(project)
@@ -54,26 +52,73 @@ module Punch
54
52
  def in(project)
55
53
  return false if in?(project)
56
54
  data[project] ||= []
57
- data[project].push({'in' => Time.now})
58
- write
55
+ time = Time.now
56
+ data[project].push({'in' => time})
57
+ log(project, "punch in @ #{time.strftime('%Y-%m-%dT%H:%M:%S%z')}")
59
58
  true
60
59
  end
61
60
 
62
- def out(project)
63
- return false if out?(project)
64
- data[project].last['out'] = Time.now
65
- write
61
+ def out(project = nil)
62
+ if project
63
+ return false unless do_out_single(project)
64
+ else
65
+ return false unless data.keys.collect { |project| do_out_single(project) }.any?
66
+ end
66
67
  true
67
68
  end
68
69
 
69
70
  def delete(project)
70
71
  return nil unless data.delete(project)
71
- write
72
72
  true
73
73
  end
74
74
 
75
- def list(project)
76
- data[project]
75
+ def list(*args)
76
+ options = args.last.is_a?(Hash) ? args.pop : {}
77
+ project = args.first
78
+ if project
79
+ do_list_single(project, options)
80
+ else
81
+ data.keys.inject({}) { |hash, project| hash.merge(project => do_list_single(project, options)) }
82
+ end
83
+ end
84
+
85
+ def total(*args)
86
+ list_data = list(*args)
87
+ if list_data.is_a?(Hash)
88
+ list_data.inject({}) { |hash, (project, project_data)| hash.merge(project => do_total_time(project_data)) }
89
+ else
90
+ return nil unless list_data
91
+ do_total_time(list_data)
92
+ end
93
+ end
94
+
95
+ def log(project, message)
96
+ return false unless in?(project)
97
+ project_data = data[project].last
98
+ project_data['log'] ||= []
99
+ project_data['log'].push message
100
+ true
101
+ end
102
+
103
+
104
+ private
105
+
106
+ def do_out_single(project)
107
+ return false if out?(project)
108
+ time = Time.now
109
+ log(project, "punch out @ #{time.strftime('%Y-%m-%dT%H:%M:%S%z')}")
110
+ data[project].last['out'] = time
111
+ end
112
+
113
+ def do_list_single(project, options)
114
+ return nil unless project_data = data[project]
115
+ project_data = project_data.select { |t| t['in'] > options[:after] } if options[:after]
116
+ project_data = project_data.select { |t| t['out'] < options[:before] } if options[:before]
117
+ project_data
118
+ end
119
+
120
+ def do_total_time(list_data)
121
+ list_data.collect { |t| ((t['out'] || Time.now) - t['in']).to_i }.inject(0) { |sum, t| sum + t }
77
122
  end
78
123
  end
79
124
  end
data/spec/punch_spec.rb CHANGED
@@ -122,7 +122,7 @@ describe Punch do
122
122
  end
123
123
  end
124
124
 
125
- it "should give a project's status" do
125
+ it 'should give project status' do
126
126
  Punch.should respond_to(:status)
127
127
  end
128
128
 
@@ -147,8 +147,8 @@ describe Punch do
147
147
  lambda { Punch.status('proj') }.should_not raise_error(ArgumentError)
148
148
  end
149
149
 
150
- it 'should require a project name' do
151
- lambda { Punch.status }.should raise_error(ArgumentError)
150
+ it 'should not require a project name' do
151
+ lambda { Punch.status }.should_not raise_error(ArgumentError)
152
152
  end
153
153
 
154
154
  it "should return 'out' if the project is currently punched out" do
@@ -178,6 +178,10 @@ describe Punch do
178
178
  Punch.status(@projects['out']).should == 'out'
179
179
  Punch.status(@projects['in']).should == 'in'
180
180
  end
181
+
182
+ it 'should return the status of all projects if no project name given' do
183
+ Punch.status.should == { @projects['out'] => 'out', @projects['in'] => 'in' }
184
+ end
181
185
  end
182
186
 
183
187
  it 'should indicate whether a project is punched out' do
@@ -273,9 +277,6 @@ describe Punch do
273
277
  end
274
278
  end
275
279
  Punch.data = @data
276
-
277
- @test = states('test').starts_as('setup')
278
- Punch.stubs(:write).when(@test.is('setup'))
279
280
  end
280
281
 
281
282
  it 'should accept a project name' do
@@ -286,25 +287,16 @@ describe Punch do
286
287
  lambda { Punch.in }.should raise_error(ArgumentError)
287
288
  end
288
289
 
289
- it 'should check whether the project is already punched in' do
290
- Punch.expects(:in?).with(@project)
291
- Punch.in(@project)
292
- end
293
-
294
290
  describe 'when the project is already punched in' do
295
291
  before :each do
296
- Punch.stubs(:in?).returns(true)
292
+ @data = { @project => [ {'in' => @now - 50, 'out' => @now - 25}, {'in' => @now - 5} ] }
293
+ Punch.data = @data
297
294
  end
298
295
 
299
296
  it 'should not change the project data' do
297
+ old_data = @data.dup
300
298
  Punch.in(@project)
301
- Punch.data.should == @data
302
- end
303
-
304
- it 'should not write the data' do
305
- @test.become('test')
306
- Punch.expects(:write).never.when(@test.is('test'))
307
- Punch.in(@project)
299
+ Punch.data.should == old_data
308
300
  end
309
301
 
310
302
  it 'should return false' do
@@ -313,10 +305,6 @@ describe Punch do
313
305
  end
314
306
 
315
307
  describe 'when the project is not already punched in' do
316
- before :each do
317
- Punch.stubs(:in?).returns(false)
318
- end
319
-
320
308
  it 'should add a time entry to the project data' do
321
309
  Punch.in(@project)
322
310
  Punch.data[@project].length.should == 2
@@ -327,9 +315,9 @@ describe Punch do
327
315
  Punch.data[@project].last['in'].should == @now
328
316
  end
329
317
 
330
- it 'should write the data' do
331
- @test.become('test')
332
- Punch.expects(:write).when(@test.is('test'))
318
+ it 'should log a message about punch-in time' do
319
+ time = @now.strftime('%Y-%m-%dT%H:%M:%S%z')
320
+ Punch.expects(:log).with(@project, "punch in @ #{time}")
333
321
  Punch.in(@project)
334
322
  end
335
323
 
@@ -358,14 +346,9 @@ describe Punch do
358
346
  Punch.data[@project].last['in'].should == @now
359
347
  end
360
348
 
361
- it 'should use now for the punch-in time' do
362
- Punch.in(@project)
363
- Punch.data[@project].last['in'].should == @now
364
- end
365
-
366
- it 'should write the data' do
367
- @test.become('test')
368
- Punch.expects(:write).when(@test.is('test'))
349
+ it 'should log a message about punch-in time' do
350
+ time = @now.strftime('%Y-%m-%dT%H:%M:%S%z')
351
+ Punch.expects(:log).with(@project, "punch in @ #{time}")
369
352
  Punch.in(@project)
370
353
  end
371
354
 
@@ -401,29 +384,15 @@ describe Punch do
401
384
  lambda { Punch.out('proj') }.should_not raise_error(ArgumentError)
402
385
  end
403
386
 
404
- it 'should require a project name' do
405
- lambda { Punch.out }.should raise_error(ArgumentError)
406
- end
407
-
408
- it 'should check whether the project is already punched out' do
409
- Punch.expects(:out?).with(@project)
410
- Punch.out(@project)
387
+ it 'should not require a project name' do
388
+ lambda { Punch.out }.should_not raise_error(ArgumentError)
411
389
  end
412
390
 
413
391
  describe 'when the project is already punched out' do
414
- before :each do
415
- Punch.stubs(:out?).returns(true)
416
- end
417
-
418
392
  it 'should not change the project data' do
393
+ old_data = @data.dup
419
394
  Punch.out(@project)
420
- Punch.data.should == @data
421
- end
422
-
423
- it 'should not write the data' do
424
- @test.become('test')
425
- Punch.expects(:write).never.when(@test.is('test'))
426
- Punch.out(@project)
395
+ Punch.data.should == old_data
427
396
  end
428
397
 
429
398
  it 'should return false' do
@@ -433,7 +402,8 @@ describe Punch do
433
402
 
434
403
  describe 'when the project is not already punched out' do
435
404
  before :each do
436
- Punch.stubs(:out?).returns(false)
405
+ @data = { @project => [ {'in' => @now - 50} ] }
406
+ Punch.data = @data
437
407
  end
438
408
 
439
409
  it 'should not add a time entry to the project data' do
@@ -446,9 +416,9 @@ describe Punch do
446
416
  Punch.data[@project].last['out'].should == @now
447
417
  end
448
418
 
449
- it 'should write the data' do
450
- @test.become('test')
451
- Punch.expects(:write).when(@test.is('test'))
419
+ it 'should log a message about punch-in time' do
420
+ time = @now.strftime('%Y-%m-%dT%H:%M:%S%z')
421
+ Punch.expects(:log).with(@project, "punch out @ #{time}")
452
422
  Punch.out(@project)
453
423
  end
454
424
 
@@ -456,6 +426,58 @@ describe Punch do
456
426
  Punch.out(@project).should == true
457
427
  end
458
428
  end
429
+
430
+ describe 'when no project is given' do
431
+ before :each do
432
+ @projects = ['test project', 'out project', 'other project']
433
+ @data = {
434
+ @projects[0] => [ {'in' => @now - 50, 'out' => @now - 25} ],
435
+ @projects[1] => [ {'in' => @now - 300, 'out' => @now - 250}, {'in' => @now - 40} ],
436
+ @projects[2] => [ {'in' => @now - 50} ],
437
+ }
438
+ Punch.data = @data
439
+ end
440
+
441
+ it 'should punch out all projects that are currently punched in' do
442
+ Punch.out
443
+ Punch.data[@projects[0]].last['out'].should == @now - 25
444
+ Punch.data[@projects[1]].last['out'].should == @now
445
+ Punch.data[@projects[2]].last['out'].should == @now
446
+ end
447
+
448
+ it 'should log punch-out messages for all projects being punched out' do
449
+ time = @now.strftime('%Y-%m-%dT%H:%M:%S%z')
450
+ Punch.expects(:log).with(@projects[1], "punch out @ #{time}")
451
+ Punch.expects(:log).with(@projects[2], "punch out @ #{time}")
452
+ Punch.out
453
+ end
454
+
455
+ it 'should return true' do
456
+ Punch.out.should == true
457
+ end
458
+
459
+ describe 'when all projects were already punched out' do
460
+ before :each do
461
+ @projects = ['test project', 'out project', 'other project']
462
+ @data = {
463
+ @projects[0] => [ {'in' => @now - 50, 'out' => @now - 25} ],
464
+ @projects[1] => [ {'in' => @now - 300, 'out' => @now - 250}, {'in' => @now - 40, 'out' => @now - 20} ],
465
+ @projects[2] => [ {'in' => @now - 50, 'out' => @now - 35} ],
466
+ }
467
+ Punch.data = @data
468
+ end
469
+
470
+ it 'should not change the data' do
471
+ old_data = @data.dup
472
+ Punch.out
473
+ Punch.data.should == old_data
474
+ end
475
+
476
+ it 'should return false' do
477
+ Punch.out.should == false
478
+ end
479
+ end
480
+ end
459
481
  end
460
482
 
461
483
  it 'should delete a project' do
@@ -493,12 +515,6 @@ describe Punch do
493
515
  Punch.data.should_not include(@project)
494
516
  end
495
517
 
496
- it 'should write the data' do
497
- @test.become('test')
498
- Punch.expects(:write).when(@test.is('test'))
499
- Punch.delete(@project)
500
- end
501
-
502
518
  it 'should return true' do
503
519
  Punch.delete(@project).should == true
504
520
  end
@@ -509,12 +525,6 @@ describe Punch do
509
525
  @project = 'non-existent project'
510
526
  end
511
527
 
512
- it 'should not write the data' do
513
- @test.become('test')
514
- Punch.expects(:write).never.when(@test.is('test'))
515
- Punch.delete(@project)
516
- end
517
-
518
528
  it 'should return nil' do
519
529
  Punch.delete(@project).should be_nil
520
530
  end
@@ -529,7 +539,7 @@ describe Punch do
529
539
  before :each do
530
540
  @now = Time.now
531
541
  @project = 'test project'
532
- @data = { @project => [ {'in' => @now - 50, 'out' => @now - 25} ] }
542
+ @data = { @project => [ {'in' => @now - 5000, 'out' => @now - 2500}, {'in' => @now - 2000, 'out' => @now - 1000}, {'in' => @now - 500, 'out' => @now - 100} ] }
533
543
 
534
544
  Punch.instance_eval do
535
545
  class << self
@@ -543,14 +553,30 @@ describe Punch do
543
553
  lambda { Punch.list('proj') }.should_not raise_error(ArgumentError)
544
554
  end
545
555
 
546
- it 'should require a project name' do
547
- lambda { Punch.list }.should raise_error(ArgumentError)
556
+ it 'should not require a project name' do
557
+ lambda { Punch.list }.should_not raise_error(ArgumentError)
558
+ end
559
+
560
+ it 'should allow options' do
561
+ lambda { Punch.list('proj', :after => Time.now) }.should_not raise_error(ArgumentError)
548
562
  end
549
563
 
550
564
  describe 'when the project exists' do
551
565
  it 'should return the project data' do
552
566
  Punch.list(@project).should == Punch.data[@project]
553
567
  end
568
+
569
+ it 'should restrict returned data to times only after a certain time' do
570
+ Punch.list(@project, :after => @now - 501).should == Punch.data[@project].last(1)
571
+ end
572
+
573
+ it 'should restrict returned data to times only before a certain time' do
574
+ Punch.list(@project, :before => @now - 2499).should == Punch.data[@project].first(1)
575
+ end
576
+
577
+ it 'should restrict returned data to times only within a time range' do
578
+ Punch.list(@project, :after => @now - 2001, :before => @now - 999).should == Punch.data[@project][1, 1]
579
+ end
554
580
  end
555
581
 
556
582
  describe 'when the project does not exist' do
@@ -561,6 +587,210 @@ describe Punch do
561
587
  it 'should return nil' do
562
588
  Punch.list(@project).should be_nil
563
589
  end
590
+
591
+ it 'should return nil if options given' do
592
+ Punch.list(@project, :after => @now - 500).should be_nil
593
+ end
594
+ end
595
+
596
+ describe 'when no project is given' do
597
+ before :each do
598
+ @projects = ['test project', 'out project', 'other project']
599
+ @data = {
600
+ @projects[0] => [ {'in' => @now - 50, 'out' => @now - 25} ],
601
+ @projects[1] => [ {'in' => @now - 300, 'out' => @now - 250}, {'in' => @now - 40, 'out' => @now - 20} ],
602
+ @projects[2] => [ {'in' => @now - 50, 'out' => @now - 35} ],
603
+ }
604
+ Punch.data = @data
605
+ end
606
+
607
+ it 'should return data for all projects' do
608
+ Punch.list.should == @data
609
+ end
610
+
611
+ it 'should respect options' do
612
+ Punch.list(:after => @now - 51).should == { @projects[0] => @data[@projects[0]], @projects[1] => @data[@projects[1]].last(1), @projects[2] => @data[@projects[2]]}
613
+ end
614
+
615
+ it 'should not change the stored data when options are given' do
616
+ old_data = @data.dup
617
+ Punch.list(:after => @now - 51)
618
+ Punch.data.should == old_data
619
+ end
620
+ end
621
+ end
622
+
623
+ it 'should get the total time for a project' do
624
+ Punch.should respond_to(:total)
625
+ end
626
+
627
+ describe 'getting total time for a project' do
628
+ before :each do
629
+ @now = Time.now
630
+ Time.stubs(:now).returns(@now)
631
+ @project = 'test project'
632
+ @data = { @project => [ {'in' => @now - 5000, 'out' => @now - 2500}, {'in' => @now - 2000, 'out' => @now - 1000}, {'in' => @now - 500, 'out' => @now - 100} ] }
633
+
634
+ Punch.instance_eval do
635
+ class << self
636
+ public :data, :data=
637
+ end
638
+ end
639
+ Punch.data = @data
640
+ end
641
+
642
+ it 'should accept a project name' do
643
+ lambda { Punch.total('proj') }.should_not raise_error(ArgumentError)
644
+ end
645
+
646
+ it 'should not require a project name' do
647
+ lambda { Punch.total }.should_not raise_error(ArgumentError)
648
+ end
649
+
650
+ it 'should allow options' do
651
+ lambda { Punch.total('proj', :after => Time.now) }.should_not raise_error(ArgumentError)
652
+ end
653
+
654
+ describe 'when the project exists' do
655
+ it 'should return the amount of time spent on the project (in seconds)' do
656
+ Punch.total(@project).should == 3900
657
+ end
658
+
659
+ it 'should restrict returned amount to times only after a certain time' do
660
+ Punch.total(@project, :after => @now - 501).should == 400
661
+ end
662
+
663
+ it 'should restrict returned amount to times only before a certain time' do
664
+ Punch.total(@project, :before => @now - 2499).should == 2500
665
+ end
666
+
667
+ it 'should restrict returned amount to times only within a time range' do
668
+ Punch.total(@project, :after => @now - 2001, :before => @now - 999).should == 1000
669
+ end
670
+
671
+ describe 'and is punched in' do
672
+ before :each do
673
+ @data[@project].push({ 'in' => @now - 25 })
674
+ Punch.data = @data
675
+ end
676
+
677
+ it 'give the time spent until now' do
678
+ Punch.total(@project).should == 3925
679
+ end
680
+ end
681
+ end
682
+
683
+ describe 'when the project does not exist' do
684
+ before :each do
685
+ @project = 'non-existent project'
686
+ end
687
+
688
+ it 'should return nil' do
689
+ Punch.total(@project).should be_nil
690
+ end
691
+ end
692
+
693
+ describe 'when no project is given' do
694
+ before :each do
695
+ @projects = ['test project', 'out project', 'other project']
696
+ @data = {
697
+ @projects[0] => [ {'in' => @now - 50, 'out' => @now - 25} ],
698
+ @projects[1] => [ {'in' => @now - 300, 'out' => @now - 250}, {'in' => @now - 40, 'out' => @now - 20} ],
699
+ @projects[2] => [ {'in' => @now - 50, 'out' => @now - 35} ],
700
+ }
701
+ Punch.data = @data
702
+ end
703
+
704
+ it 'should give totals for all projects' do
705
+ Punch.total.should == { @projects[0] => 25, @projects[1] => 70, @projects[2] => 15}
706
+ end
707
+
708
+ it 'should respect options' do
709
+ Punch.total(:after => @now - 51).should == { @projects[0] => 25, @projects[1] => 20, @projects[2] => 15}
710
+ end
711
+ end
712
+ end
713
+
714
+ it 'should log information about a project' do
715
+ Punch.should respond_to(:log)
716
+ end
717
+
718
+ describe 'logging information about a project' do
719
+ before :each do
720
+ @now = Time.now
721
+ @project = 'test project'
722
+ @data = { @project => [ {'in' => @now - 50, 'log' => ['some earlier message']} ] }
723
+
724
+ Punch.instance_eval do
725
+ class << self
726
+ public :data, :data=
727
+ end
728
+ end
729
+ Punch.data = @data
730
+
731
+ @message = 'some log message'
732
+ end
733
+
734
+ it 'should accept a project and message' do
735
+ lambda { Punch.log('proj', 'some mess') }.should_not raise_error(ArgumentError)
736
+ end
737
+
738
+ it 'should require a message' do
739
+ lambda { Punch.log('proj') }.should raise_error(ArgumentError)
740
+ end
741
+
742
+ it 'should require a project' do
743
+ lambda { Punch.log }.should raise_error(ArgumentError)
744
+ end
745
+
746
+ it 'should check if the project is punched in' do
747
+ Punch.expects(:in?).with(@project)
748
+ Punch.log(@project, @message)
749
+ end
750
+
751
+ describe 'when the project is punched in' do
752
+ it 'should add a log message to the last time entry for the project' do
753
+ Punch.log(@project, @message)
754
+ Punch.data[@project].last['log'].length.should == 2
755
+ end
756
+
757
+ it 'should use the given message for the log' do
758
+ Punch.log(@project, @message)
759
+ Punch.data[@project].last['log'].last.should == @message
760
+ end
761
+
762
+ it 'should return true' do
763
+ Punch.log(@project, @message).should == true
764
+ end
765
+
766
+ describe 'and has no log' do
767
+ before :each do
768
+ @data = { @project => [ {'in' => @now - 50} ] }
769
+ Punch.data = @data
770
+ end
771
+
772
+ it 'should create the log' do
773
+ Punch.log(@project, @message)
774
+ Punch.data[@project].last['log'].should == [@message]
775
+ end
776
+ end
777
+ end
778
+
779
+ describe 'when the project is not punched in' do
780
+ before :each do
781
+ @data = { @project => [ {'in' => @now - 50, 'out' => @now - 25, 'log' => ['some earlier message']} ] }
782
+ Punch.data = @data
783
+ end
784
+
785
+ it 'should not change the project data' do
786
+ old_data = @data.dup
787
+ Punch.log(@project, @message)
788
+ Punch.data.should == old_data
789
+ end
790
+
791
+ it 'should return false' do
792
+ Punch.log(@project, @message).should == false
793
+ end
564
794
  end
565
795
  end
566
796
  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.0.2
4
+ version: 0.0.3
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-08-22 00:00:00 -05:00
12
+ date: 2008-08-25 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency