timetrap 1.7.0 → 1.7.1

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/.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