timetrap 1.7.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  tags
2
2
  pkg
3
3
  *.swp
4
+ Gemfile.lock
5
+ .bundle
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --profile
data/CONTRIBUTORS CHANGED
@@ -2,3 +2,4 @@
2
2
  * Ian Smith-Heisters
3
3
  * Ammar Ali
4
4
  * Brian V. Hughes
5
+ * Thorben Schröder
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'sequel', ">= 3.9.0"
4
+ gem 'sqlite3-ruby', ">= 1.2.5"
5
+ gem 'chronic', "~> 0.3.0"
6
+ gem 'getopt-declare', ">= 1.28"
7
+ gem 'json', "~>1.4.6"
8
+ gem 'icalendar', "~>1.1.5"
9
+
10
+ group :test do
11
+ gem "rspec", "~>2"
12
+ gem 'fakefs'
13
+ end
data/README.md CHANGED
@@ -65,7 +65,7 @@ You check out with the `out` command.
65
65
  Checked out of sheet "coding"
66
66
 
67
67
  You can edit entries that aren't running using `edit`'s `--id` or `-i` flag.
68
- `t display --verbose` will tell you the ids.
68
+ `t display --ids` (or `t display -v`) will tell you the ids.
69
69
 
70
70
  $ # note id column in output
71
71
  $ t d -v
@@ -292,6 +292,13 @@ Commands
292
292
 
293
293
  usage: ``t out [--at TIME] [TIMESHEET]``
294
294
 
295
+ **resume**
296
+ Start the timer for the current timesheet with the same notes as the last entry.
297
+ If there is no last entry the new one has blank notes ore uses the optional
298
+ NOTES parameter.
299
+
300
+ usage: ``t resume [--at TIME] [NOTES]``
301
+
295
302
  **sheet**
296
303
  Switch to a timesheet creating it if necessary. The default timesheet is
297
304
  called "default". When no sheet is specified list all existing sheets.
data/Rakefile CHANGED
@@ -1,12 +1,14 @@
1
- require 'spec/rake/spectask'
2
1
  require 'rake/rdoctask'
3
2
  require 'rake/gempackagetask'
3
+ require 'rspec/core/rake_task'
4
4
 
5
+ desc 'Default: run specs.'
5
6
  task :default => :spec
6
7
 
7
- desc "Run all specs in spec directory"
8
- Spec::Rake::SpecTask.new(:spec) do |t|
9
- t.spec_files = FileList['spec/**/*_spec.rb']
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
+ # Put spec opts in a file named .rspec in root
10
12
  end
11
13
 
12
14
  Rake::RDocTask.new do |rd|
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 1
3
3
  :minor: 7
4
- :patch: 0
4
+ :patch: 1
data/lib/timetrap.rb CHANGED
@@ -1,4 +1,5 @@
1
- require 'rubygems'
1
+ require "rubygems"
2
+
2
3
  require 'chronic'
3
4
  require 'sequel'
4
5
  require 'yaml'
data/lib/timetrap/cli.rb CHANGED
@@ -73,6 +73,11 @@ COMMAND is one of:
73
73
  usage: t out [--at TIME] [TIMESHEET]
74
74
  -a, --at <time:qs> Use this time instead of now
75
75
 
76
+ * resume - Start the timer for the current time sheet with the same note as
77
+ the last entry on the sheet. If there is no entry it takes the passed note.
78
+ usage: t resume [--at TIME] [NOTES]
79
+ -a, --at <time:qs> Use this time instead of now
80
+
76
81
  * sheet - Switch to a timesheet creating it if necessary. When no sheet is
77
82
  specified list all sheets.
78
83
  usage: t sheet [TIMESHEET]
@@ -210,6 +215,17 @@ COMMAND is one of:
210
215
  warn "Checked into sheet #{Timer.current_sheet.inspect}."
211
216
  end
212
217
 
218
+ def resume
219
+ last_entry = Timer.entries(Timer.current_sheet).last
220
+ warn "No entry yet on this sheet yet. Started a new entry." unless last_entry
221
+ note = (last_entry ? last_entry.note : nil)
222
+ warn "Resuming #{note.inspect} from entry ##{last_entry.id}" if note
223
+
224
+ self.unused_args = note || unused_args
225
+
226
+ self.in
227
+ end
228
+
213
229
  def out
214
230
  sheet = sheet_name_from_string(unused_args)
215
231
  if Timer.stop sheet, args['-a']
@@ -315,6 +331,10 @@ COMMAND is one of:
315
331
  args.unused.join(' ')
316
332
  end
317
333
 
334
+ def unused_args=(str)
335
+ args.unused = str.split
336
+ end
337
+
318
338
  def ask_user question
319
339
  return true if args['-y']
320
340
  $stderr.print question
@@ -36,8 +36,8 @@ module Timetrap
36
36
  else
37
37
  Timetrap::Entry.filter('sheet = ?', Timer.current_sheet)
38
38
  end
39
- ee = ee.filter('start >= ?', Date.parse(Chronic.parse(args['-s']).to_s)) if args['-s']
40
- ee = ee.filter('start <= ?', Date.parse(Chronic.parse(args['-e']).to_s) + 1) if args['-e']
39
+ ee = ee.filter('start >= ?', Date.parse(Timer.process_time(args['-s']).to_s)) if args['-s']
40
+ ee = ee.filter('start <= ?', Date.parse(Timer.process_time(args['-e']).to_s) + 1) if args['-e']
41
41
  ee
42
42
  end
43
43
 
@@ -14,11 +14,11 @@ module Timetrap
14
14
  end
15
15
 
16
16
  def start= time
17
- self[:start]= process_time(time)
17
+ self[:start]= Timer.process_time(time)
18
18
  end
19
19
 
20
20
  def end= time
21
- self[:end]= process_time(time)
21
+ self[:end]= Timer.process_time(time)
22
22
  end
23
23
 
24
24
  def start
@@ -57,35 +57,6 @@ module Timetrap
57
57
  end
58
58
 
59
59
  private
60
- def process_time(time)
61
- case time
62
- when Time
63
- time
64
- when String
65
- if parsed = Chronic.parse(time)
66
- parsed
67
- elsif safe_for_time_parse?(time) and parsed = Time.parse(time)
68
- parsed
69
- else
70
- raise ArgumentError, "Could not parse #{time.inspect}, entry not updated"
71
- end
72
- end
73
- end
74
-
75
- # Time.parse is optimistic and will parse things like '=18' into midnight
76
- # on 18th of this month.
77
- # It will also turn 'total garbage' into Time.now
78
- # Here we do some sanity checks on the string to protect it from common
79
- # cli formatting issues, and allow reasonable warning to be passed back to
80
- # the user.
81
- def safe_for_time_parse?(string)
82
- # misformatted cli option
83
- !string.include?('=') and
84
- # a date time string needs a number in it
85
- string =~ /\d/
86
- end
87
-
88
-
89
60
  # do a quick pseudo migration. This should only get executed on the first run
90
61
  set_schema do
91
62
  primary_key :id
@@ -8,6 +8,40 @@ module Timetrap
8
8
 
9
9
  extend self
10
10
 
11
+ def process_time(time)
12
+ case time
13
+ when Time
14
+ time
15
+ when String
16
+ chronic = begin
17
+ Chronic.parse(time)
18
+ rescue => e
19
+ warn "#{e.class} in Chronic gem parsing time. Falling back to Time.parse"
20
+ end
21
+
22
+ if parsed = chronic
23
+ parsed
24
+ elsif safe_for_time_parse?(time) and parsed = Time.parse(time)
25
+ parsed
26
+ else
27
+ raise ArgumentError, "Could not parse #{time.inspect}, entry not updated"
28
+ end
29
+ end
30
+ end
31
+
32
+ # Time.parse is optimistic and will parse things like '=18' into midnight
33
+ # on 18th of this month.
34
+ # It will also turn 'total garbage' into Time.now
35
+ # Here we do some sanity checks on the string to protect it from common
36
+ # cli formatting issues, and allow reasonable warning to be passed back to
37
+ # the user.
38
+ def safe_for_time_parse?(string)
39
+ # misformatted cli option
40
+ !string.include?('=') and
41
+ # a date time string needs a number in it
42
+ string =~ /\d/
43
+ end
44
+
11
45
  def current_sheet= sheet
12
46
  m = Meta.find_or_create(:key => 'current_sheet')
13
47
  m.value = sheet
@@ -1,8 +1,16 @@
1
1
  TEST_MODE = true
2
2
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'timetrap'))
3
- require 'spec'
3
+ require 'rspec'
4
4
  require 'fakefs/safe'
5
5
 
6
+ def local_time(str)
7
+ Timetrap::Timer.process_time(str)
8
+ end
9
+
10
+ def local_time_cli(str)
11
+ local_time(str).strftime('%Y-%m-%d %H:%M:%S')
12
+ end
13
+
6
14
  module Timetrap::StubConfig
7
15
  def with_stubbed_config options
8
16
  options.each do |k, v|
@@ -197,7 +205,7 @@ The "format" command is deprecated in favor of "display". Sorry for the inconven
197
205
  :note => 'entry 4', :start => '2008-10-05 18:00:00'
198
206
  )
199
207
 
200
- Time.stub!(:now).and_return Time.at(1223254800 + (60*60*2))
208
+ Time.stub!(:now).and_return local_time('2008-10-05 20:00:00')
201
209
  @desired_output = <<-OUTPUT
202
210
  Timesheet: SpecSheet
203
211
  Day Start End Duration Notes
@@ -310,7 +318,6 @@ Grand Total 10:00:00
310
318
  RUBY
311
319
  end
312
320
  invoke 'd -fbaz'
313
- $stderr.string.should == ''
314
321
  $stdout.string.should == "yeah I did it\n"
315
322
  FileUtils.rm_r dir
316
323
  end
@@ -381,28 +388,29 @@ start,end,note,sheet
381
388
 
382
389
  describe 'json' do
383
390
  before do
384
- create_entry(:start => '2008-10-03 12:00:00', :end => '2008-10-03 14:00:00')
385
- create_entry(:start => '2008-10-05 12:00:00', :end => '2008-10-05 14:00:00')
391
+ create_entry(:start => local_time_cli('2008-10-03 12:00:00'), :end => local_time_cli('2008-10-03 14:00:00'))
392
+ create_entry(:start => local_time_cli('2008-10-05 12:00:00'), :end => local_time_cli('2008-10-05 14:00:00'))
386
393
  end
387
394
 
388
395
  it "should export to json not including running items" do
389
396
  invoke 'in'
390
397
  invoke 'display -f json'
391
398
  JSON.parse($stdout.string).should == JSON.parse(<<-EOF)
392
- [{\"sheet\":\"default\",\"end\":\"Fri Oct 03 14:00:00 -0700 2008\",\"start\":\"Fri Oct 03 12:00:00 -0700 2008\",\"note\":\"note\",\"id\":1},{\"sheet\":\"default\",\"end\":\"Sun Oct 05 14:00:00 -0700 2008\",\"start\":\"Sun Oct 05 12:00:00 -0700 2008\",\"note\":\"note\",\"id\":2}]
399
+ [{\"sheet\":\"default\",\"end\":\"#{local_time('2008-10-03 14:00:00')}\",\"start\":\"#{local_time('2008-10-03 12:00:00')}\",\"note\":\"note\",\"id\":1},{\"sheet\":\"default\",\"end\":\"#{local_time('2008-10-05 14:00:00')}\",\"start\":\"#{local_time('2008-10-05 12:00:00')}\",\"note\":\"note\",\"id\":2}]
393
400
  EOF
394
401
  end
395
402
  end
396
403
 
397
404
  describe 'ical' do
398
405
  before do
399
- create_entry(:start => '2008-10-03 12:00:00', :end => '2008-10-03 14:00:00')
400
- create_entry(:start => '2008-10-05 12:00:00', :end => '2008-10-05 14:00:00')
406
+ create_entry(:start => local_time_cli('2008-10-03 12:00:00'), :end => local_time_cli('2008-10-03 14:00:00'))
407
+ create_entry(:start => local_time_cli('2008-10-05 12:00:00'), :end => local_time_cli('2008-10-05 14:00:00'))
401
408
  end
402
409
 
403
410
  it "should not export running items" do
404
411
  invoke 'in'
405
412
  invoke 'display --format ical'
413
+
406
414
  $stdout.string.scan(/BEGIN:VEVENT/).should have(2).item
407
415
  end
408
416
 
@@ -533,17 +541,17 @@ END:VCALENDAR
533
541
 
534
542
  describe "with sheets defined" do
535
543
  before do
536
- Time.stub!(:now).and_return Time.parse("Oct 5 18:00:00 -0700 2008")
537
- create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-03 12:00:00',
538
- :end => '2008-10-03 14:00:00')
539
- create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-03 12:00:00',
540
- :end => '2008-10-03 14:00:00')
541
- create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-05 12:00:00',
542
- :end => '2008-10-05 14:00:00')
543
- create_entry( :sheet => 'A Longly Named Sheet 2', :start => '2008-10-05 14:00:00',
544
+ Time.stub!(:now).and_return local_time("2008-10-05 18:00:00")
545
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => local_time_cli('2008-10-03 12:00:00'),
546
+ :end => local_time_cli('2008-10-03 14:00:00'))
547
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => local_time_cli('2008-10-03 12:00:00'),
548
+ :end => local_time_cli('2008-10-03 14:00:00'))
549
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => local_time_cli('2008-10-05 12:00:00'),
550
+ :end => local_time_cli('2008-10-05 14:00:00'))
551
+ create_entry( :sheet => 'A Longly Named Sheet 2', :start => local_time_cli('2008-10-05 14:00:00'),
544
552
  :end => nil)
545
- create_entry( :sheet => 'Sheet 1', :start => '2008-10-03 16:00:00',
546
- :end => '2008-10-03 18:00:00')
553
+ create_entry( :sheet => 'Sheet 1', :start => local_time_cli('2008-10-03 16:00:00'),
554
+ :end => local_time_cli('2008-10-03 18:00:00'))
547
555
  Timetrap::Timer.current_sheet = 'A Longly Named Sheet 2'
548
556
  end
549
557
  it "should list available timesheets" do
@@ -656,6 +664,56 @@ END:VCALENDAR
656
664
  end
657
665
  end
658
666
 
667
+ describe "resume" do
668
+ before :each do
669
+ @time = Time.now
670
+ Time.stub!(:now).and_return @time
671
+
672
+ invoke 'in Some strange task'
673
+ @last_active = Timetrap::Timer.active_entry
674
+ invoke 'out'
675
+
676
+ Timetrap::Timer.active_entry.should be_nil
677
+ @last_active.should_not be_nil
678
+ end
679
+
680
+ it "should allow to resume the last active sheet" do
681
+ invoke 'resume'
682
+
683
+ Timetrap::Timer.active_entry.note.should ==(@last_active.note)
684
+ Timetrap::Timer.active_entry.start.to_s.should == @time.to_s
685
+ end
686
+
687
+ it "should allow to resume the activity with a given time" do
688
+ invoke 'resume --at "10am 2008-10-03"'
689
+
690
+ Timetrap::Timer.active_entry.start.should eql(Time.parse('2008-10-03 10:00'))
691
+ end
692
+
693
+ describe "no existing entries" do
694
+ before(:each) do
695
+ Timetrap::Timer.entries(Timetrap::Timer.current_sheet).each do |e|
696
+ e.destroy
697
+ end
698
+
699
+ Timetrap::Timer.entries(Timetrap::Timer.current_sheet).should be_empty
700
+ Timetrap::Timer.active_entry.should be_nil
701
+ end
702
+
703
+ it "starts a new entry if no entry exists" do
704
+ invoke "resume"
705
+ Timetrap::Timer.active_entry.should_not be_nil
706
+ Timetrap::Timer.active_entry.note.should ==("")
707
+ end
708
+
709
+ it "allows to pass a note that is used for the new entry" do
710
+ invoke "resume New Note"
711
+ Timetrap::Timer.active_entry.should_not be_nil
712
+ Timetrap::Timer.active_entry.note.should ==("New Note")
713
+ end
714
+ end
715
+ end
716
+
659
717
  describe "sheet" do
660
718
  it "should switch to a new timesheet" do
661
719
  invoke 'sheet sheet 1'
data/timetrap.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{timetrap}
8
- s.version = "1.7.0"
8
+ s.version = "1.7.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Sam Goldstein"]
12
- s.date = %q{2010-12-08}
12
+ s.date = %q{2010-12-18}
13
13
  s.default_executable = %q{t}
14
14
  s.description = %q{Command line time tracker}
15
15
  s.email = %q{sgrock@gmail.com}
@@ -20,7 +20,9 @@ Gem::Specification.new do |s|
20
20
  ]
21
21
  s.files = [
22
22
  ".gitignore",
23
+ ".rspec",
23
24
  "CONTRIBUTORS",
25
+ "Gemfile",
24
26
  "LICENSE.txt",
25
27
  "README.md",
26
28
  "Rakefile",
@@ -39,7 +41,6 @@ Gem::Specification.new do |s|
39
41
  "lib/timetrap/helpers.rb",
40
42
  "lib/timetrap/models.rb",
41
43
  "lib/timetrap/timer.rb",
42
- "spec/spec.opts",
43
44
  "spec/timetrap_spec.rb",
44
45
  "timetrap.gemspec"
45
46
  ]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timetrap
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 9
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 7
9
- - 0
10
- version: 1.7.0
9
+ - 1
10
+ version: 1.7.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sam Goldstein
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-08 00:00:00 -08:00
18
+ date: 2010-12-18 00:00:00 -08:00
19
19
  default_executable: t
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -92,7 +92,9 @@ extra_rdoc_files:
92
92
  - README.md
93
93
  files:
94
94
  - .gitignore
95
+ - .rspec
95
96
  - CONTRIBUTORS
97
+ - Gemfile
96
98
  - LICENSE.txt
97
99
  - README.md
98
100
  - Rakefile
@@ -111,7 +113,6 @@ files:
111
113
  - lib/timetrap/helpers.rb
112
114
  - lib/timetrap/models.rb
113
115
  - lib/timetrap/timer.rb
114
- - spec/spec.opts
115
116
  - spec/timetrap_spec.rb
116
117
  - timetrap.gemspec
117
118
  has_rdoc: true
data/spec/spec.opts DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --format profile