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 +2 -0
- data/.rspec +2 -0
- data/CONTRIBUTORS +1 -0
- data/Gemfile +13 -0
- data/README.md +8 -1
- data/Rakefile +6 -4
- data/VERSION.yml +1 -1
- data/lib/timetrap.rb +2 -1
- data/lib/timetrap/cli.rb +20 -0
- data/lib/timetrap/helpers.rb +2 -2
- data/lib/timetrap/models.rb +2 -31
- data/lib/timetrap/timer.rb +34 -0
- data/spec/timetrap_spec.rb +76 -18
- data/timetrap.gemspec +4 -3
- metadata +6 -5
- data/spec/spec.opts +0 -2
data/.rspec
ADDED
data/CONTRIBUTORS
CHANGED
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 --
|
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
|
8
|
-
|
9
|
-
t.
|
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
data/lib/timetrap.rb
CHANGED
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
|
data/lib/timetrap/helpers.rb
CHANGED
@@ -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(
|
40
|
-
ee = ee.filter('start <= ?', Date.parse(
|
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
|
|
data/lib/timetrap/models.rb
CHANGED
@@ -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
|
data/lib/timetrap/timer.rb
CHANGED
@@ -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
|
data/spec/timetrap_spec.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
TEST_MODE = true
|
2
2
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'timetrap'))
|
3
|
-
require '
|
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
|
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\":\"
|
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
|
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.
|
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-
|
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:
|
4
|
+
hash: 9
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 7
|
9
|
-
-
|
10
|
-
version: 1.7.
|
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-
|
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