one_inch_punch 0.0.2 → 0.0.3

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,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