samg-timetrap 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/bin/dev_t ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # Executable with absolute path to lib for hacking and development
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'timetrap')
4
+ Timetrap::CLI.invoke
@@ -0,0 +1,227 @@
1
+ module Timetrap
2
+ module CLI
3
+ extend Helpers
4
+ attr_accessor :args
5
+ extend self
6
+
7
+ USAGE = <<-EOF
8
+
9
+ Timetrap - Simple Time Tracking
10
+
11
+ Usage: #{File.basename $0} COMMAND [OPTIONS] [ARGS...]
12
+
13
+ where COMMAND is one of:
14
+ * alter - alter an entry's note, start, or end time. Defaults to the active entry
15
+ usage: t alter [--id ID] [--start TIME] [--end TIME] [NOTES]
16
+ -i, --id <id:i> Alter entry with id <id> instead of the running entry
17
+ -s, --start <time:qs> Change the start time to <time>
18
+ -e, --end <time:qs> Change the end time to <time>
19
+ * backend - open an sqlite shell to the database
20
+ usage: t backend
21
+ * display - display the current timesheet
22
+ usage: t display [--ids] [--start DATE] [--end DATE] [TIMESHEET]
23
+ -v, --ids Print database ids (for use with alter)
24
+ -s, --start <date:qs> Include entries that start on this date or later
25
+ -e, --end <date:qs> Include entries that start on this date or earlier
26
+ * format - export a sheet to csv format
27
+ usage: t format [--ids] [--start DATE] [--end DATE] FORMATTER
28
+ FORMATTER Currently only supports 'ical'
29
+ -s, --start <date:qs> Include entries that start on this date or later
30
+ -e, --end <date:qs> Include entries that start on this date or earlier
31
+ * in - start the timer for the current timesheet
32
+ usage: t in [--at TIME] [NOTES]
33
+ -a, --at <time:qs> Use this time instead of now
34
+ * kill - delete a timesheet
35
+ usage: t kill [--id ID] [TIMESHEET]
36
+ -i, --id <id:i> Alter entry with id <id> instead of the running entry
37
+ * list - show the available timesheets
38
+ usage: t list
39
+ * now - show the status of the current timesheet
40
+ usage: t now
41
+ * out - stop the timer for the current timesheet
42
+ usage: t out [--at TIME]
43
+ -a, --at <time:qs> Use this time instead of now
44
+ * running - show all running timesheets
45
+ NOT IMPLEMENTED
46
+ * switch - switch to a new timesheet
47
+ usage: t switch TIMESHEET
48
+
49
+ OTHER OPTIONS
50
+ -h, --help Display this help
51
+ EOF
52
+
53
+ def parse arguments
54
+ args.parse arguments
55
+ end
56
+
57
+ def invoke
58
+ args['-h'] ? say(USAGE) : invoke_command_if_valid
59
+ end
60
+
61
+ def commands
62
+ Timetrap::CLI::USAGE.scan(/\* \w+/).map{|s| s.gsub(/\* /, '')}
63
+ end
64
+
65
+ def say *something
66
+ puts *something
67
+ end
68
+
69
+ def invoke_command_if_valid
70
+ command = args.unused.shift
71
+ case (valid = commands.select{|name| name =~ %r|^#{command}|}).size
72
+ when 0 then say "Invalid command: #{command}"
73
+ when 1 then send valid[0]
74
+ else; say "Ambigous command: #{command}"; end
75
+ end
76
+
77
+ def alter
78
+ entry = args['-i'] ? Entry[args['-i']] : Timetrap.active_entry
79
+ say "can't find entry" && return unless entry
80
+ entry.update :start => args['-s'] if args['-s'] =~ /.+/
81
+ entry.update :end => args['-e'] if args['-e'] =~ /.+/
82
+ entry.update :note => unused_args if unused_args =~ /.+/
83
+ end
84
+
85
+ def backend
86
+ exec "sqlite3 #{DB_NAME}"
87
+ end
88
+
89
+ def in
90
+ Timetrap.start unused_args, args['-a']
91
+ end
92
+
93
+ def out
94
+ Timetrap.stop args['-a']
95
+ end
96
+
97
+ def kill
98
+ if e = Entry[args['-i']]
99
+ out = "are you sure you want to delete entry #{e.id}? "
100
+ out << "(#{e.note}) " if e.note.to_s =~ /.+/
101
+ print out
102
+ if $stdin.gets =~ /\Aye?s?\Z/i
103
+ e.destroy
104
+ say "it's dead"
105
+ else
106
+ say "will not kill"
107
+ end
108
+ elsif (sheets = Entry.map{|e| e.sheet }.uniq).include?(sheet = unused_args)
109
+ victims = Entry.filter(:sheet => sheet).count
110
+ print "are you sure you want to delete #{victims} entries on sheet #{sheet.inspect}? "
111
+ if $stdin.gets =~ /\Aye?s?\Z/i
112
+ Timetrap.kill_sheet sheet
113
+ say "killed #{victims} entries"
114
+ else
115
+ say "will not kill"
116
+ end
117
+ else
118
+ victim = args['-i'] ? args['-i'].to_s.inspect : sheet.inspect
119
+ say "can't find #{victim} to kill", 'sheets:', *sheets
120
+ end
121
+ end
122
+
123
+ def display
124
+ sheet = sheet_name_from_string(unused_args)
125
+ sheet = (sheet =~ /.+/ ? sheet : Timetrap.current_sheet)
126
+ say "Timesheet: #{sheet}"
127
+ id_heading = args['-v'] ? 'Id' : ' '
128
+ say "#{id_heading} Day Start End Duration Notes"
129
+ last_start = nil
130
+ from_current_day = []
131
+ ee = Timetrap::Entry.filter(:sheet => sheet).order(:start)
132
+ ee = ee.filter(:end >= Date.parse(args['-s'])) if args['-s']
133
+ ee = ee.filter(:start <= Date.parse(args['-e']) + 1) if args['-e']
134
+ ee.each_with_index do |e, i|
135
+
136
+
137
+ from_current_day << e
138
+ e_end = e.end || Time.now
139
+ say "%-4s%16s%11s -%9s%10s %s" % [
140
+ (args['-v'] ? e.id : ''),
141
+ format_date_if_new(e.start, last_start),
142
+ format_time(e.start),
143
+ format_time(e.end),
144
+ format_duration(e.start, e_end),
145
+ e.note
146
+ ]
147
+
148
+ nxt = ee.map[i+1]
149
+ if nxt == nil or !same_day?(e.start, nxt.start)
150
+ say "%52s" % format_total(from_current_day)
151
+ from_current_day = []
152
+ else
153
+ end
154
+ last_start = e.start
155
+ end
156
+ say <<-OUT
157
+ ---------------------------------------------------------
158
+ OUT
159
+ say " Total%43s" % format_total(ee)
160
+ end
161
+
162
+ # TODO: Consolidate display and format
163
+ def format
164
+ begin
165
+ fmt_klass = Timetrap::Formatters.const_get("#{unused_args.classify}")
166
+ rescue
167
+ say "Invalid format specified `#{unused_args}'"
168
+ return
169
+ end
170
+ ee = Timetrap::Entry.filter(:sheet => Timetrap.current_sheet)
171
+ ee = ee.filter(:end >= Date.parse(args['-s'])) if args['-s']
172
+ ee = ee.filter(:start <= Date.parse(args['-e']) + 1) if args['-e']
173
+ say Timetrap.format(fmt_klass,ee.all)
174
+ end
175
+
176
+ def switch
177
+ sheet = unused_args
178
+ if not sheet =~ /.+/ then say "No sheet specified"; return end
179
+ say "Switching to sheet " + Timetrap.switch(sheet)
180
+ end
181
+
182
+ def list
183
+ sheets = Entry.map{|e|e.sheet}.uniq.sort.map do |sheet|
184
+ sheet_atts = {:total => 0, :running => 0, :today => 0}
185
+ DB[:entries].filter(:sheet => sheet).inject(sheet_atts) do |m, e|
186
+ e_end = e[:end] || Time.now
187
+ m[:name] ||= sheet
188
+ m[:total] += (e_end.to_i - e[:start].to_i)
189
+ m[:running] += (e_end.to_i - e[:start].to_i) unless e[:end]
190
+ m[:today] += (e_end.to_i - e[:start].to_i) if same_day?(Time.now, e[:start])
191
+ m
192
+ end
193
+ end
194
+ width = sheets.sort_by{|h|h[:name].length }.last[:name].length + 4
195
+ say " %-#{width}s%-12s%-12s%s" % ["Timesheet", "Running", "Today", "Total Time"]
196
+ sheets.each do |sheet|
197
+ star = sheet[:name] == Timetrap.current_sheet ? '*' : ' '
198
+ say "#{star}%-#{width}s%-12s%-12s%s" % [
199
+ sheet[:running],
200
+ sheet[:today],
201
+ sheet[:total]
202
+ ].map(&method(:format_seconds)).unshift(sheet[:name])
203
+ end
204
+ end
205
+
206
+ def now
207
+ if Timetrap.running?
208
+ out = "#{Timetrap.current_sheet}: #{format_duration(Timetrap.active_entry.start, Time.now)}".gsub(/ /, ' ')
209
+ out << " (#{Timetrap.active_entry.note})" if Timetrap.active_entry.note =~ /.+/
210
+ say out
211
+ else
212
+ say "#{Timetrap.current_sheet}: not running"
213
+ end
214
+ end
215
+
216
+ def running
217
+ say "Sorry not implemented yet :-("
218
+ end
219
+
220
+ private
221
+
222
+ def unused_args
223
+ args.unused.join(' ')
224
+ end
225
+
226
+ end
227
+ end
@@ -0,0 +1,44 @@
1
+ module Timetrap
2
+ module Helpers
3
+ def format_time time
4
+ return '' unless time.respond_to?(:strftime)
5
+ time.strftime('%H:%M:%S')
6
+ end
7
+
8
+ def format_date time
9
+ return '' unless time.respond_to?(:strftime)
10
+ time.strftime('%a %b %d, %Y')
11
+ end
12
+
13
+ def format_date_if_new time, last_time
14
+ return '' unless time.respond_to?(:strftime)
15
+ same_day?(time, last_time) ? '' : format_date(time)
16
+ end
17
+
18
+ def same_day? time, other_time
19
+ format_date(time) == format_date(other_time)
20
+ end
21
+
22
+ def format_duration stime, etime
23
+ return '' unless stime and etime
24
+ secs = etime.to_i - stime.to_i
25
+ format_seconds secs
26
+ end
27
+
28
+ def format_seconds secs
29
+ "%2s:%02d:%02d" % [secs/3600, (secs%3600)/60, secs%60]
30
+ end
31
+
32
+ def format_total entries
33
+ secs = entries.inject(0){|m, e|e_end = e.end || Time.now; m += e_end.to_i - e.start.to_i if e_end && e.start;m}
34
+ "%2s:%02d:%02d" % [secs/3600, (secs%3600)/60, secs%60]
35
+ end
36
+
37
+ def sheet_name_from_string string
38
+ return "" unless string =~ /.+/
39
+ DB[:entries].filter(:sheet.like("#{string}%")).first[:sheet]
40
+ rescue
41
+ ""
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ module Timetrap
2
+ class Entry < Sequel::Model
3
+ plugin :schema
4
+
5
+ def start= time
6
+ self[:start]= Chronic.parse(time) || time
7
+ end
8
+
9
+ def end= time
10
+ self[:end]= Chronic.parse(time) || time
11
+ end
12
+
13
+ # do a quick pseudo migration. This should only get executed on the first run
14
+ set_schema do
15
+ primary_key :id
16
+ column :note, :string
17
+ column :start, :timestamp
18
+ column :end, :timestamp
19
+ column :sheet, :string
20
+ end
21
+ create_table unless table_exists?
22
+ end
23
+
24
+ class Meta < Sequel::Model(:meta)
25
+ plugin :schema
26
+
27
+ set_schema do
28
+ primary_key :id
29
+ column :key, :string
30
+ column :value, :string
31
+ end
32
+ create_table unless table_exists?
33
+ end
34
+ end
data/lib/timetrap.rb CHANGED
@@ -1,262 +1,20 @@
1
1
  require 'rubygems'
2
2
  require 'chronic'
3
3
  require 'sequel'
4
+ require 'sequel/extensions/inflector'
4
5
  require 'Getopt/Declare'
5
- # connect to database. This will create one if it doesn't exist
6
+ require File.join(File.dirname(__FILE__), 'timetrap', 'helpers')
7
+ require File.join(File.dirname(__FILE__), 'timetrap', 'cli')
6
8
  DB_NAME = defined?(TEST_MODE) ? nil : "#{ENV['HOME']}/.timetrap.db"
9
+ # connect to database. This will create one if it doesn't exist
7
10
  DB = Sequel.sqlite DB_NAME
8
-
11
+ require File.join(File.dirname(__FILE__), 'timetrap', 'models')
12
+ Dir["#{File.dirname(__FILE__)}/timetrap/formatters/*.rb"].each do |path|
13
+ require path
14
+ end
9
15
  module Timetrap
10
16
  extend self
11
17
 
12
- module CLI
13
- attr_accessor :args
14
- extend self
15
-
16
- USAGE = <<-EOF
17
-
18
- Timetrap - Simple Time Tracking
19
-
20
- Usage: #{File.basename $0} COMMAND [OPTIONS] [ARGS...]
21
-
22
- where COMMAND is one of:
23
- * alter - alter an entry's note, start, or end time. Defaults to the active entry
24
- usage: t alter [--id ID] [--start TIME] [--end TIME] [NOTES]
25
- -i, --id <id:i> Alter entry with id <id> instead of the running entry
26
- -s, --start <time:qs> Change the start time to <time>
27
- -e, --end <time:qs> Change the end time to <time>
28
- * backend - open an sqlite shell to the database
29
- usage: t backend
30
- * display - display the current timesheet
31
- usage: t display [--ids] [TIMESHEET]
32
- -v, --ids Print database ids (for use with alter)
33
- * format - export a sheet to csv format
34
- NOT IMPLEMENTED
35
- * in - start the timer for the current timesheet
36
- usage: t in [--at TIME] [NOTES]
37
- -a, --at <time:qs> Use this time instead of now
38
- * kill - delete a timesheet
39
- usage: t kill [--id ID] [TIMESHEET]
40
- -i, --id <id:i> Alter entry with id <id> instead of the running entry
41
- * list - show the available timesheets
42
- usage: t list
43
- * now - show the status of the current timesheet
44
- usage: t now
45
- * out - stop the timer for the current timesheet
46
- usage: t out [--at TIME]
47
- -a, --at <time:qs> Use this time instead of now
48
- * running - show all running timesheets
49
- NOT IMPLEMENTED
50
- * switch - switch to a new timesheet
51
- usage: t switch TIMESHEET
52
-
53
- OTHER OPTIONS
54
- -h, --help Display this help
55
- EOF
56
-
57
- def parse arguments
58
- args.parse arguments
59
- end
60
-
61
- def invoke
62
- args['-h'] ? say(USAGE) : invoke_command_if_valid
63
- end
64
-
65
- def commands
66
- Timetrap::CLI::USAGE.scan(/\* \w+/).map{|s| s.gsub(/\* /, '')}
67
- end
68
-
69
- def invoke_command_if_valid
70
- command = args.unused.shift
71
- case (valid = commands.select{|name| name =~ %r|^#{command}|}).size
72
- when 0 then say "Invalid command: #{command}"
73
- when 1 then send valid[0]
74
- else; say "Ambigous command: #{command}"; end
75
- end
76
-
77
- def alter
78
- entry = args['-i'] ? Entry[args['-i']] : Timetrap.active_entry
79
- say "can't find entry" && return unless entry
80
- entry.update :start => args['-s'] if args['-s'] =~ /.+/
81
- entry.update :end => args['-e'] if args['-e'] =~ /.+/
82
- entry.update :note => unused_args if unused_args =~ /.+/
83
- end
84
-
85
- def backend
86
- exec "sqlite3 #{DB_NAME}"
87
- end
88
-
89
- def in
90
- Timetrap.start unused_args, args['-a']
91
- end
92
-
93
- def out
94
- Timetrap.stop args['-a']
95
- end
96
-
97
- def kill
98
- if e = Entry[args['-i']]
99
- out = "are you sure you want to delete entry #{e.id}? "
100
- out << "(#{e.note}) " if e.note.to_s =~ /.+/
101
- print out
102
- if $stdin.gets =~ /\Aye?s?\Z/i
103
- e.destroy
104
- say "it's dead"
105
- else
106
- say "will not kill"
107
- end
108
- elsif (sheets = Entry.map{|e| e.sheet }.uniq).include?(sheet = unused_args)
109
- victims = Entry.filter(:sheet => sheet).count
110
- print "are you sure you want to delete #{victims} entries on sheet #{sheet.inspect}? "
111
- if $stdin.gets =~ /\Aye?s?\Z/i
112
- Timetrap.kill_sheet sheet
113
- say "killed #{victims} entries"
114
- else
115
- say "will not kill"
116
- end
117
- else
118
- victim = args['-i'] ? args['-i'].to_s.inspect : sheet.inspect
119
- say "can't find #{victim} to kill", 'sheets:', *sheets
120
- end
121
- end
122
-
123
- def display
124
- sheet = sheet_name_from_string(unused_args)
125
- sheet = (sheet =~ /.+/ ? sheet : Timetrap.current_sheet)
126
- say "Timesheet: #{sheet}"
127
- id_heading = args['-v'] ? 'Id' : ' '
128
- say "#{id_heading} Day Start End Duration Notes"
129
- last_start = nil
130
- from_current_day = []
131
- (ee = Timetrap.entries(sheet)).each_with_index do |e, i|
132
-
133
-
134
- from_current_day << e
135
- e_end = e.end || Time.now
136
- say "%-4s%16s%11s -%9s%10s %s" % [
137
- (args['-v'] ? e.id : ''),
138
- format_date_if_new(e.start, last_start),
139
- format_time(e.start),
140
- format_time(e.end),
141
- format_duration(e.start, e_end),
142
- e.note
143
- ]
144
-
145
- nxt = Timetrap.entries(sheet).map[i+1]
146
- if nxt == nil or !same_day?(e.start, nxt.start)
147
- say "%52s" % format_total(from_current_day)
148
- from_current_day = []
149
- else
150
- end
151
- last_start = e.start
152
- end
153
- say <<-OUT
154
- ---------------------------------------------------------
155
- OUT
156
- say " Total%43s" % format_total(ee)
157
- end
158
-
159
- def format
160
- say "Sorry not implemented yet :-("
161
- end
162
-
163
- def switch
164
- sheet = args.unused.join(' ')
165
- if not sheet =~ /.+/ then say "No sheet specified"; return end
166
- say "Switching to sheet " + Timetrap.switch(sheet)
167
- end
168
-
169
- def list
170
- sheets = Entry.map{|e|e.sheet}.uniq.sort.map do |sheet|
171
- sheet_atts = {:total => 0, :running => 0, :today => 0}
172
- DB[:entries].filter(:sheet => sheet).inject(sheet_atts) do |m, e|
173
- e_end = e[:end] || Time.now
174
- m[:name] ||= sheet
175
- m[:total] += (e_end.to_i - e[:start].to_i)
176
- m[:running] += (e_end.to_i - e[:start].to_i) unless e[:end]
177
- m[:today] += (e_end.to_i - e[:start].to_i) if same_day?(Time.now, e[:start])
178
- m
179
- end
180
- end
181
- width = sheets.sort_by{|h|h[:name].length }.last[:name].length + 4
182
- say " %-#{width}s%-12s%-12s%s" % ["Timesheet", "Running", "Today", "Total Time"]
183
- sheets.each do |sheet|
184
- star = sheet[:name] == Timetrap.current_sheet ? '*' : ' '
185
- say "#{star}%-#{width}s%-12s%-12s%s" % [
186
- sheet[:running],
187
- sheet[:today],
188
- sheet[:total]
189
- ].map(&method(:format_seconds)).unshift(sheet[:name])
190
- end
191
- end
192
-
193
- def now
194
- if Timetrap.running?
195
- out = "#{Timetrap.current_sheet}: #{format_duration(Timetrap.active_entry.start, Time.now)}".gsub(/ /, ' ')
196
- out << " (#{Timetrap.active_entry.note})" if Timetrap.active_entry.note =~ /.+/
197
- say out
198
- else
199
- say "#{Timetrap.current_sheet}: not running"
200
- end
201
- end
202
-
203
- def running
204
- say "Sorry not implemented yet :-("
205
- end
206
-
207
- private
208
-
209
- def format_time time
210
- return '' unless time.respond_to?(:strftime)
211
- time.strftime('%H:%M:%S')
212
- end
213
-
214
- def format_date time
215
- return '' unless time.respond_to?(:strftime)
216
- time.strftime('%a %b %d, %Y')
217
- end
218
-
219
- def format_date_if_new time, last_time
220
- return '' unless time.respond_to?(:strftime)
221
- same_day?(time, last_time) ? '' : format_date(time)
222
- end
223
-
224
- def same_day? time, other_time
225
- format_date(time) == format_date(other_time)
226
- end
227
-
228
- def format_duration stime, etime
229
- return '' unless stime and etime
230
- secs = etime.to_i - stime.to_i
231
- format_seconds secs
232
- end
233
-
234
- def format_seconds secs
235
- "%2s:%02d:%02d" % [secs/3600, (secs%3600)/60, secs%60]
236
- end
237
-
238
- def format_total entries
239
- secs = entries.inject(0){|m, e|e_end = e.end || Time.now; m += e_end.to_i - e.start.to_i if e_end && e.start;m}
240
- "%2s:%02d:%02d" % [secs/3600, (secs%3600)/60, secs%60]
241
- end
242
-
243
- def sheet_name_from_string string
244
- return "" unless string =~ /.+/
245
- DB[:entries].filter(:sheet.like("#{string}%")).first[:sheet]
246
- rescue
247
- ""
248
- end
249
-
250
- def unused_args
251
- args.unused.join(' ')
252
- end
253
-
254
- public
255
- def say *something
256
- puts *something
257
- end
258
- end
259
-
260
18
  def current_sheet= sheet
261
19
  m = Meta.find_or_create(:key => 'current_sheet')
262
20
  m.value = sheet
@@ -306,45 +64,16 @@ where COMMAND is one of:
306
64
  Entry.filter(:sheet => sheet).destroy
307
65
  end
308
66
 
67
+ def format format_klass, entries
68
+ format_klass.new(entries).output
69
+ end
70
+
309
71
  class AlreadyRunning < StandardError
310
72
  def message
311
73
  "Timetrap is already running"
312
74
  end
313
75
  end
314
76
 
315
-
316
- class Entry < Sequel::Model
317
- plugin :schema
318
-
319
- def start= time
320
- self[:start]= Chronic.parse(time) || time
321
- end
322
-
323
- def end= time
324
- self[:end]= Chronic.parse(time) || time
325
- end
326
-
327
- # do a quick pseudo migration. This should only get executed on the first run
328
- set_schema do
329
- primary_key :id
330
- column :note, :string
331
- column :start, :timestamp
332
- column :end, :timestamp
333
- column :sheet, :string
334
- end
335
- create_table unless table_exists?
336
- end
337
-
338
- class Meta < Sequel::Model(:meta)
339
- plugin :schema
340
-
341
- set_schema do
342
- primary_key :id
343
- column :key, :string
344
- column :value, :string
345
- end
346
- create_table unless table_exists?
347
- end
348
77
  CLI.args = Getopt::Declare.new(<<-EOF)
349
78
  #{CLI::USAGE}
350
79
  EOF
@@ -5,7 +5,7 @@ require 'spec'
5
5
  describe Timetrap do
6
6
  def create_entry atts = {}
7
7
  Timetrap::Entry.create({
8
- :sheet => 's1',
8
+ :sheet => 'default',
9
9
  :start => Time.now,
10
10
  :end => Time.now,
11
11
  :note => 'note'}.merge(atts))
@@ -136,8 +136,42 @@ Id Day Start End Duration Notes
136
136
  end
137
137
 
138
138
  describe "format" do
139
- it "should export a sheet to a csv format" do
140
- pending
139
+ describe 'ical' do
140
+ before do
141
+ create_entry(:start => '2008-10-03 12:00:00', :end => '2008-10-03 14:00:00')
142
+ create_entry(:start => '2008-10-05 12:00:00', :end => '2008-10-05 14:00:00')
143
+ end
144
+
145
+ it "should filter events by the passed dates" do
146
+ invoke 'format ical --start 2008-10-03 --end 2008-10-03'
147
+ $stdout.string.scan(/BEGIN:VEVENT/).should have(1).item
148
+ end
149
+
150
+ it "should not filter events by date when none are passed" do
151
+ invoke 'format ical'
152
+ $stdout.string.scan(/BEGIN:VEVENT/).should have(2).item
153
+ end
154
+
155
+ it "should export a sheet to an ical format" do
156
+ invoke 'format ical --start 2008-10-03 --end 2008-10-03'
157
+ desired = <<-EOF
158
+ BEGIN:VCALENDAR
159
+ VERSION:2.0
160
+ CALSCALE:GREGORIAN
161
+ METHOD:PUBLISH
162
+ PRODID:iCalendar-Ruby
163
+ BEGIN:VEVENT
164
+ SEQUENCE:0
165
+ DTEND:20081003T140000
166
+ SUMMARY:note
167
+ DTSTART:20081003T120000
168
+ END:VEVENT
169
+ END:VCALENDAR
170
+ EOF
171
+ desired.each_line do |line|
172
+ $stdout.string.should =~ /#{line.chomp}/
173
+ end
174
+ end
141
175
  end
142
176
  end
143
177
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samg-timetrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Goldstein
@@ -71,12 +71,18 @@ extensions: []
71
71
  extra_rdoc_files: []
72
72
 
73
73
  files:
74
- - README.md
75
74
  - LICENSE.txt
76
75
  - Rakefile
77
- - lib/timetrap.rb
76
+ - README.md
77
+ - bin/dev_t
78
78
  - bin/t
79
+ - lib/timetrap
80
+ - lib/timetrap.rb
79
81
  - spec/timetrap_spec.rb
82
+ - lib/timetrap/cli.rb
83
+ - lib/timetrap/formatters
84
+ - lib/timetrap/helpers.rb
85
+ - lib/timetrap/models.rb
80
86
  has_rdoc: true
81
87
  homepage: http://github.com/samg/timetrap/tree/master
82
88
  post_install_message: