punch 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +6 -6
- data/bin/punch +363 -262
- data/gemspec.rb +1 -1
- metadata +53 -46
data/README
CHANGED
@@ -2,13 +2,11 @@ NAME
|
|
2
2
|
punch
|
3
3
|
|
4
4
|
SYNOPSIS
|
5
|
-
punch (in|out|log|clock|status|list|total|delete|dump) [options]+
|
6
|
-
|
7
|
-
URIS
|
8
|
-
http://codeforpeople.com/lib/ruby/punch
|
9
|
-
http://rubyforge.org/projects/codeforpeople/
|
5
|
+
punch (in|out|log|clock|status|list|total|delete|dump|parse) [options]+
|
10
6
|
|
11
7
|
DESCRIPTION
|
8
|
+
i can has time tracking!
|
9
|
+
|
12
10
|
punch is a k.i.s.s. tool for tracking the hours spent on various projects.
|
13
11
|
it supports logging hours under a project name, adding notes about work
|
14
12
|
done during that period, and several very simple reporting tools that
|
@@ -21,7 +19,9 @@ PARAMETERS
|
|
21
19
|
|
22
20
|
EXAMPLES
|
23
21
|
. punch in projectname
|
24
|
-
. punch
|
22
|
+
. punch in projectname 'an hour ago'
|
23
|
+
. punch log projectname 'rewriting your java app in ruby...'
|
25
24
|
. punch out projectname
|
26
25
|
. punch total projectname --after 2007-12-03 --before 2007-12-07
|
26
|
+
. punch total projectname --after 'yesterday morning' --before 'today at noon'
|
27
27
|
|
data/bin/punch
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Main {
|
4
4
|
description <<-txt
|
5
|
+
i can has time tracking!
|
6
|
+
|
5
7
|
punch is a k.i.s.s. tool for tracking the hours spent on various projects.
|
6
8
|
it supports logging hours under a project name, adding notes about work
|
7
9
|
done during that period, and several very simple reporting tools that
|
@@ -12,9 +14,11 @@ Main {
|
|
12
14
|
|
13
15
|
examples <<-txt
|
14
16
|
. punch in projectname
|
15
|
-
. punch
|
17
|
+
. punch in projectname 'an hour ago'
|
18
|
+
. punch log projectname 'rewriting your java app in ruby...'
|
16
19
|
. punch out projectname
|
17
20
|
. punch total projectname --after 2007-12-03 --before 2007-12-07
|
21
|
+
. punch total projectname --after 'yesterday morning' --before 'today at noon'
|
18
22
|
txt
|
19
23
|
|
20
24
|
|
@@ -23,29 +27,32 @@ Main {
|
|
23
27
|
|
24
28
|
examples <<-txt
|
25
29
|
. punch in projectname
|
26
|
-
. punch in projectname
|
27
|
-
. punch in projectname
|
28
|
-
. punch in projectname
|
30
|
+
. punch in projectname 2007-01-01T09
|
31
|
+
. punch in projectname now
|
32
|
+
. punch in projectname 'today at around noon'
|
33
|
+
. punch in projectname 2007-01-01T09 --message 'new years day'
|
29
34
|
txt
|
30
35
|
|
31
|
-
mixin :argument_project, :option_db, :
|
36
|
+
mixin :argument_project, :optional_now, :option_db, :option_message, :option_noop
|
32
37
|
|
33
38
|
run{ y db.punch(:in, project, 'now' => now, 'message' => message) }
|
34
39
|
}
|
35
40
|
|
36
41
|
|
37
42
|
mode(:out){
|
38
|
-
description 'punch out of a project'
|
43
|
+
description 'punch out of a project or, iff no project given, all open projects'
|
39
44
|
|
40
45
|
examples <<-txt
|
41
46
|
. punch out projectname
|
42
|
-
. punch out projectname
|
43
|
-
. punch out projectname
|
44
|
-
. punch out projectname
|
45
|
-
. punch out projectname
|
47
|
+
. punch out projectname 2007-01-01T09
|
48
|
+
. punch out projectname now
|
49
|
+
. punch out projectname 2007-01-01T17 --message 'new years day'
|
50
|
+
. punch out projectname 2007-01-01T05pm -m 'new years day'
|
51
|
+
. punch out projectname an hour ago
|
52
|
+
. punch out
|
46
53
|
txt
|
47
54
|
|
48
|
-
mixin :
|
55
|
+
mixin :optional_project, :optional_now, :option_db, :option_message, :option_noop
|
49
56
|
|
50
57
|
run{ y db.punch(:out, project, 'now' => now, 'message' => message) }
|
51
58
|
}
|
@@ -55,11 +62,11 @@ Main {
|
|
55
62
|
description 'log a message for a project you are currently logged into'
|
56
63
|
|
57
64
|
examples <<-txt
|
58
|
-
. punch log projectname '
|
65
|
+
. punch log projectname 'xml - welcome to my pain cave!'
|
59
66
|
. punch log projectname should have used yaml
|
60
67
|
txt
|
61
68
|
|
62
|
-
mixin :argument_project, :option_db, :argument_message
|
69
|
+
mixin :argument_project, :option_db, :argument_message, :option_noop
|
63
70
|
|
64
71
|
run{ y db.log(project, message) }
|
65
72
|
}
|
@@ -72,7 +79,7 @@ Main {
|
|
72
79
|
. punch clock projectname 2007-01-01T09am 2007-01-01T05pm -m "working on new year's day"
|
73
80
|
txt
|
74
81
|
|
75
|
-
mixin :argument_project, :argument_punch_in, :argument_punch_out, :option_db, :option_message
|
82
|
+
mixin :argument_project, :argument_punch_in, :argument_punch_out, :option_db, :option_message, :option_noop
|
76
83
|
|
77
84
|
run{ y db.clock(project, punch_in, punch_out, 'message' => message) }
|
78
85
|
}
|
@@ -127,11 +134,9 @@ Main {
|
|
127
134
|
. punch delete projectname
|
128
135
|
txt
|
129
136
|
|
130
|
-
mixin :argument_project, :option_db
|
137
|
+
mixin :argument_project, :option_db, :option_noop
|
131
138
|
|
132
|
-
run{
|
133
|
-
y db.delete(project)
|
134
|
-
}
|
139
|
+
run{ y db.delete(project) }
|
135
140
|
}
|
136
141
|
|
137
142
|
mode(:dump){
|
@@ -147,6 +152,23 @@ Main {
|
|
147
152
|
run{ y db.dump(project) }
|
148
153
|
}
|
149
154
|
|
155
|
+
mode(:parse){
|
156
|
+
description 'show how a time string will be parsed'
|
157
|
+
|
158
|
+
examples <<-txt
|
159
|
+
. punch parse 'yesterday morning'
|
160
|
+
. punch parse today at noon
|
161
|
+
. punch parse yesterday at 9pm
|
162
|
+
txt
|
163
|
+
|
164
|
+
argument(:spec){
|
165
|
+
arity -2
|
166
|
+
attr
|
167
|
+
}
|
168
|
+
|
169
|
+
run{ y Time.parse(spec.join(' ')) }
|
170
|
+
}
|
171
|
+
|
150
172
|
|
151
173
|
mixin :argument_project do
|
152
174
|
argument(:project){
|
@@ -190,8 +212,8 @@ Main {
|
|
190
212
|
end
|
191
213
|
|
192
214
|
mixin :option_now do
|
193
|
-
option(:now){
|
194
|
-
|
215
|
+
option(:now, :n){
|
216
|
+
argument :required
|
195
217
|
desc 'consider this time as the current time'
|
196
218
|
cast{|value| Time.parse value}
|
197
219
|
default Time::Now
|
@@ -199,6 +221,16 @@ Main {
|
|
199
221
|
}
|
200
222
|
end
|
201
223
|
|
224
|
+
mixin :optional_now do
|
225
|
+
argument(:now){
|
226
|
+
optional
|
227
|
+
desc 'consider this time as the current time'
|
228
|
+
arity -2
|
229
|
+
default Time::Now
|
230
|
+
}
|
231
|
+
attribute(:now){ Time.parse param['now'].values.join(' ') }
|
232
|
+
end
|
233
|
+
|
202
234
|
mixin :option_db do
|
203
235
|
option(:db){
|
204
236
|
argument :required
|
@@ -207,6 +239,12 @@ Main {
|
|
207
239
|
}
|
208
240
|
end
|
209
241
|
|
242
|
+
mixin :option_noop do
|
243
|
+
option(:noop, :N){
|
244
|
+
cast{|value| $NOOP = value}
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
210
248
|
mixin :option_after do
|
211
249
|
option(:after, :A){
|
212
250
|
argument :required
|
@@ -231,306 +269,369 @@ Main {
|
|
231
269
|
}
|
232
270
|
|
233
271
|
|
234
|
-
BEGIN {
|
235
|
-
require 'yaml'
|
236
|
-
require 'yaml/store'
|
237
|
-
require 'time'
|
238
|
-
require 'pathname'
|
239
272
|
|
240
|
-
begin
|
241
|
-
require 'rubygems'
|
242
|
-
gem 'main', '~> 2.6.0'
|
243
|
-
rescue
|
244
|
-
42
|
245
|
-
end
|
246
273
|
|
247
|
-
begin
|
248
|
-
require 'main'
|
249
|
-
rescue
|
250
|
-
abort "gem install main"
|
251
|
-
end
|
252
274
|
|
253
|
-
begin
|
254
|
-
require 'orderedhash'
|
255
|
-
rescue
|
256
|
-
abort "gem install orderedhash"
|
257
|
-
end
|
258
275
|
|
259
|
-
Home = File.expand_path(ENV['HOME'] || '~')
|
260
276
|
|
261
|
-
|
277
|
+
#
|
278
|
+
# bootstrap libs and litter some crap into ruby built-ins ;-)
|
279
|
+
#
|
262
280
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
281
|
+
BEGIN {
|
282
|
+
require 'yaml'
|
283
|
+
require 'yaml/store'
|
284
|
+
require 'time'
|
285
|
+
require 'pathname'
|
286
|
+
require 'tempfile'
|
287
|
+
require 'fileutils'
|
270
288
|
|
271
|
-
|
272
|
-
|
289
|
+
begin
|
290
|
+
require 'rubygems'
|
291
|
+
rescue
|
292
|
+
42
|
293
|
+
end
|
273
294
|
|
274
|
-
|
295
|
+
begin
|
296
|
+
require 'main'
|
297
|
+
rescue
|
298
|
+
abort "gem install main"
|
299
|
+
end
|
275
300
|
|
276
|
-
|
301
|
+
begin
|
302
|
+
require 'orderedhash'
|
303
|
+
rescue
|
304
|
+
abort "gem install orderedhash"
|
305
|
+
end
|
277
306
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
307
|
+
begin
|
308
|
+
require 'chronic'
|
309
|
+
rescue
|
310
|
+
abort "gem install chronic"
|
282
311
|
end
|
283
312
|
|
284
|
-
|
285
|
-
|
313
|
+
Home = File.expand_path(ENV['HOME'] || '~')
|
314
|
+
|
315
|
+
### hackity hack, don't talk back
|
286
316
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
317
|
+
class Object
|
318
|
+
def yaml_inline!
|
319
|
+
class << self
|
320
|
+
def to_yaml_style() :inline end
|
321
|
+
end
|
292
322
|
end
|
293
|
-
Parse.call string
|
294
323
|
end
|
295
324
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
325
|
+
class Time
|
326
|
+
Beginning = Time.at(0).iso8601
|
327
|
+
|
328
|
+
End = Time.at((2**31)-1).iso8601
|
329
|
+
|
330
|
+
Now = Time.now.iso8601
|
331
|
+
|
332
|
+
Null = Time.at(0).instance_eval do
|
333
|
+
def to_s() "" end
|
334
|
+
def inspect() "" end
|
335
|
+
self
|
336
|
+
end
|
337
|
+
|
338
|
+
def to_s(n=0) iso8601(n) end
|
339
|
+
alias_method 'inspect', 'to_s'
|
340
|
+
|
341
|
+
### hack to fix Time.parse bug
|
342
|
+
Parse = Time.method 'parse'
|
343
|
+
def self.parse string
|
344
|
+
if string =~ %r'^\d\d\d\d-\d\d-\d\dT\d\d:?$'
|
345
|
+
string = string.sub(%r/:$/,'') + ':00'
|
346
|
+
end
|
347
|
+
if string =~ %r/\ban\b/
|
348
|
+
string = string.sub(%r/\ban\b/, '1')
|
313
349
|
end
|
314
|
-
|
315
|
-
|
316
|
-
standard += "%s" % [tz]
|
317
|
-
if to_yaml_properties.empty?
|
318
|
-
out.scalar( taguri, standard, :plain )
|
350
|
+
if string =~ %r'^\d\d\d\d-\d\d-\d\d'
|
351
|
+
Parse.call string
|
319
352
|
else
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
353
|
+
Chronic.parse string, :context => :past
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def to_yaml( opts = {} )
|
358
|
+
YAML::quick_emit( object_id, opts ) do |out|
|
359
|
+
tz = "Z"
|
360
|
+
# from the tidy Tobias Peters <t-peters@gmx.de> Thanks!
|
361
|
+
unless self.utc?
|
362
|
+
utc_same_instant = self.dup.utc
|
363
|
+
utc_same_writing = Time.utc(year,month,day,hour,min,sec,usec)
|
364
|
+
difference_to_utc = utc_same_writing - utc_same_instant
|
365
|
+
if (difference_to_utc < 0)
|
366
|
+
difference_sign = '-'
|
367
|
+
absolute_difference = -difference_to_utc
|
368
|
+
else
|
369
|
+
difference_sign = '+'
|
370
|
+
absolute_difference = difference_to_utc
|
371
|
+
end
|
372
|
+
difference_minutes = (absolute_difference/60).round
|
373
|
+
tz = "%s%02d:%02d" % [ difference_sign, difference_minutes / 60, difference_minutes % 60]
|
374
|
+
end
|
375
|
+
standard = self.strftime( "%Y-%m-%dT%H:%M:%S" )
|
376
|
+
standard += ".%02d" % [usec] #if usec.nonzero?
|
377
|
+
standard += "%s" % [tz]
|
378
|
+
if to_yaml_properties.empty?
|
379
|
+
out.scalar( taguri, standard, :plain )
|
380
|
+
else
|
381
|
+
out.map( taguri, to_yaml_style ) do |map|
|
382
|
+
map.add( 'at', standard )
|
383
|
+
to_yaml_properties.each do |m|
|
384
|
+
map.add( m, instance_variable_get( m ) )
|
385
|
+
end
|
324
386
|
end
|
325
|
-
|
387
|
+
end
|
326
388
|
end
|
327
389
|
end
|
328
390
|
end
|
329
|
-
end #}}}
|
330
391
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
392
|
+
class Numeric
|
393
|
+
def hms s = self.to_i
|
394
|
+
h, s = s.divmod 3600
|
395
|
+
m, s = s.divmod 60
|
396
|
+
[h.to_i, m.to_i, s]
|
397
|
+
end
|
336
398
|
end
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
return self[key] if has_key?(key)
|
344
|
-
default
|
399
|
+
|
400
|
+
class String
|
401
|
+
def hms
|
402
|
+
h, m, s = split %r/:/, 3
|
403
|
+
[h.to_i, m.to_i, s.to_i]
|
404
|
+
end
|
345
405
|
end
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
@pathname = Pathname.new pathname.to_s
|
354
|
-
@db = YAML::Store.new @pathname.to_s
|
355
|
-
end #}}}
|
356
|
-
|
357
|
-
def punch which, project, options = {} #{{{
|
358
|
-
now = options.getopt 'now'
|
359
|
-
message = options.getopt 'message'
|
360
|
-
message ||= "punch #{ which } @ #{ now }"
|
361
|
-
@db.transaction do
|
362
|
-
create project
|
363
|
-
list = @db[project.to_s]
|
364
|
-
entry = list.last
|
365
|
-
case which.to_s
|
366
|
-
when /in/
|
367
|
-
if(entry and entry['out'].nil?)
|
368
|
-
abort 'you first need to punch out'
|
369
|
-
else
|
370
|
-
entry = create_entry('in' => now, 'out' => nil, 'log' => [message].flatten.compact)
|
371
|
-
list << entry
|
372
|
-
end
|
373
|
-
when /out/
|
374
|
-
if(entry.nil? or (entry and (entry['in'].nil? or entry['out'])))
|
375
|
-
abort 'you first need to punch in'
|
376
|
-
else
|
377
|
-
entry = create_entry entry
|
378
|
-
entry['out'] = now
|
379
|
-
entry['log'] = [entry['log'], message].flatten.compact
|
380
|
-
list[-1] = entry
|
381
|
-
end
|
382
|
-
end
|
383
|
-
entry
|
406
|
+
|
407
|
+
class Hash
|
408
|
+
def getopt key, default = nil
|
409
|
+
return self[key.to_s] if has_key?(key.to_s)
|
410
|
+
return self[key.to_s.to_sym] if has_key?(key.to_s.to_sym)
|
411
|
+
return self[key] if has_key?(key)
|
412
|
+
default
|
384
413
|
end
|
385
|
-
end
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
if
|
394
|
-
|
414
|
+
end
|
415
|
+
|
416
|
+
class DB
|
417
|
+
attr 'pathname'
|
418
|
+
attr 'db'
|
419
|
+
|
420
|
+
def initialize pathname
|
421
|
+
@pathname = Pathname.new pathname.to_s
|
422
|
+
if $NOOP
|
423
|
+
t = Tempfile.new Process.pid.to_s
|
424
|
+
t.write IO.read(pathname)
|
425
|
+
t.close
|
426
|
+
@db = YAML::Store.new t.path
|
427
|
+
else
|
428
|
+
@db = YAML::Store.new @pathname.to_s
|
395
429
|
end
|
396
|
-
entry = create_entry entry
|
397
|
-
entry['log'] = [entry['log'], message].flatten.compact
|
398
|
-
list[-1] = entry
|
399
|
-
entry
|
400
430
|
end
|
401
|
-
end #}}}
|
402
431
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
"punch
|
432
|
+
def punch which, project, options = {}
|
433
|
+
now = options.getopt 'now'
|
434
|
+
messages = [
|
435
|
+
"punch #{ which } @ #{ now }",
|
407
436
|
options.getopt('message'),
|
408
|
-
"punch out @ #{ punch_out }",
|
409
437
|
].flatten.compact
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
438
|
+
@db.transaction do
|
439
|
+
if project
|
440
|
+
create project
|
441
|
+
list = @db[project.to_s]
|
442
|
+
entry = list.last
|
443
|
+
case which.to_s
|
444
|
+
when /in/
|
445
|
+
if(entry and entry['out'].nil?)
|
446
|
+
abort 'you first need to punch out'
|
447
|
+
else
|
448
|
+
entry = create_entry('in' => now, 'out' => nil, 'log' => [messages].flatten.compact)
|
449
|
+
list << entry
|
450
|
+
end
|
451
|
+
when /out/
|
452
|
+
if(entry.nil? or (entry and (entry['in'].nil? or entry['out'])))
|
453
|
+
abort 'you first need to punch in'
|
454
|
+
else
|
455
|
+
entry = create_entry entry
|
456
|
+
sum = now - Time.parse(entry['in'].to_s)
|
457
|
+
entry['out'] = now
|
458
|
+
entry['log'] = [entry['log'], messages].flatten.compact
|
459
|
+
entry['total'] = ('%0.2d:%0.2d:%0.2d' % sum.hms)
|
460
|
+
list[-1] = entry
|
461
|
+
end
|
428
462
|
end
|
429
|
-
|
430
|
-
|
431
|
-
|
463
|
+
entry
|
464
|
+
else
|
465
|
+
entries = OrderedHash.new
|
466
|
+
@db.roots.each do |project|
|
467
|
+
list = @db[project]
|
468
|
+
next unless Array === list
|
469
|
+
list.each do |entry|
|
470
|
+
next unless entry
|
471
|
+
if(entry['in'] and entry['out'].nil?)
|
472
|
+
entry = create_entry entry
|
473
|
+
sum = now - Time.parse(entry['in'].to_s)
|
474
|
+
entry['out'] = now
|
475
|
+
entry['log'] = [entry['log'], messages].flatten.compact
|
476
|
+
entry['total'] = ('%0.2d:%0.2d:%0.2d' % sum.hms)
|
477
|
+
list[-1] = entry
|
478
|
+
(entries[project] ||= []) << entry
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
entries
|
483
|
+
end
|
432
484
|
end
|
433
485
|
end
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
entry =
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
486
|
+
|
487
|
+
def log project, message, options = {}
|
488
|
+
@db.transaction do
|
489
|
+
create project
|
490
|
+
message = [message].flatten.compact.join(' ')
|
491
|
+
list = @db[project.to_s]
|
492
|
+
entry = list.last
|
493
|
+
if(entry.nil? or (entry and entry['out']))
|
494
|
+
abort 'you first need to punch in'
|
495
|
+
end
|
496
|
+
entry = create_entry entry
|
497
|
+
entry['log'] = [entry['log'], message].flatten.compact
|
498
|
+
list[-1] = entry
|
499
|
+
entry
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def clock project, punch_in, punch_out, options = {}
|
504
|
+
@db.transaction do
|
505
|
+
log = [
|
506
|
+
"punch in @ #{ punch_in }",
|
507
|
+
options.getopt('message'),
|
508
|
+
"punch out @ #{ punch_out }",
|
509
|
+
].flatten.compact
|
510
|
+
create project
|
511
|
+
list = @db[project.to_s]
|
512
|
+
entry = create_entry('in' => punch_in, 'out' => punch_out, 'log' => log)
|
513
|
+
list << entry
|
514
|
+
@db[project.to_s] = list.sort_by{|e| e.values_at('in', 'out')}
|
515
|
+
entry
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
def list project, options = {}
|
520
|
+
window = options['window'] || options[:window]
|
521
|
+
@db.transaction do
|
522
|
+
create project
|
450
523
|
if project
|
451
|
-
|
524
|
+
@db[project.to_s].select do |entry|
|
525
|
+
a, b = entry.values_at 'in', 'out'
|
526
|
+
if a and b
|
527
|
+
window === Time.parse(a.to_s) and window === Time.parse(b.to_s)
|
528
|
+
end
|
529
|
+
end.map{|entry| create_entry entry}
|
452
530
|
else
|
453
|
-
|
531
|
+
@db.roots
|
454
532
|
end
|
455
533
|
end
|
456
|
-
h
|
457
534
|
end
|
458
|
-
end #}}}
|
459
535
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
536
|
+
def status project, options = {}
|
537
|
+
@db.transaction do
|
538
|
+
h = Hash.new
|
539
|
+
@db.roots.each do |root|
|
540
|
+
entry = @db[root].last
|
541
|
+
a, b =
|
542
|
+
if entry
|
543
|
+
entry.values_at 'in', 'out'
|
544
|
+
else
|
545
|
+
[false, 'out']
|
546
|
+
end
|
547
|
+
#status = b ? "out @ #{ b }" : "in @ #{ a }"
|
548
|
+
#status = b ? ['out', b] : ['in', a]
|
549
|
+
#status.yaml_inline!
|
550
|
+
status = b ? Hash['out', b] : Hash['in', a]
|
551
|
+
if project
|
552
|
+
h[project] = status if project == root
|
553
|
+
else
|
554
|
+
h[root] = status
|
555
|
+
end
|
468
556
|
end
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
557
|
+
h
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
def total project, options = {}
|
562
|
+
window = options['window'] || options[:window]
|
563
|
+
@db.transaction do
|
564
|
+
if project
|
565
|
+
create project
|
473
566
|
selected = @db[project.to_s].select do |entry|
|
474
567
|
a, b = entry.values_at 'in', 'out'
|
475
568
|
window === Time.parse(a.to_s) and window === Time.parse(b.to_s)
|
476
569
|
end
|
477
|
-
filtered
|
570
|
+
filtered = { project => selected }
|
571
|
+
else
|
572
|
+
filtered = Hash.new
|
573
|
+
@db.roots.each do |project|
|
574
|
+
selected = @db[project.to_s].select do |entry|
|
575
|
+
a, b = entry.values_at 'in', 'out'
|
576
|
+
window === Time.parse(a.to_s) and window === Time.parse(b.to_s)
|
577
|
+
end
|
578
|
+
filtered.update project => selected
|
579
|
+
end
|
478
580
|
end
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
581
|
+
total = Hash.new
|
582
|
+
filtered.each do |project, selected|
|
583
|
+
sum = 0
|
584
|
+
selected.each do |entry|
|
585
|
+
a, b = entry.values_at 'in', 'out'
|
586
|
+
next unless a and b
|
587
|
+
a = Time.parse(a.to_s)
|
588
|
+
b = Time.parse(b.to_s)
|
589
|
+
sum += (b - a)
|
590
|
+
end
|
591
|
+
total.update project => ('%0.2d:%0.2d:%0.2d' % sum.hms)
|
489
592
|
end
|
490
|
-
total
|
593
|
+
total
|
491
594
|
end
|
492
|
-
total
|
493
595
|
end
|
494
|
-
end #}}}
|
495
596
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
597
|
+
def delete project
|
598
|
+
@db.transaction do
|
599
|
+
@db.delete project
|
600
|
+
project
|
601
|
+
end
|
500
602
|
end
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
if project
|
507
|
-
dumped[project] = Array.new
|
508
|
-
@db[project].each do |entry|
|
509
|
-
dumped[project] << create_entry(entry)
|
510
|
-
end
|
511
|
-
else
|
512
|
-
@db.roots.each do |project|
|
603
|
+
|
604
|
+
def dump project
|
605
|
+
@db.transaction do
|
606
|
+
dumped = OrderedHash.new
|
607
|
+
if project
|
513
608
|
dumped[project] = Array.new
|
514
609
|
@db[project].each do |entry|
|
515
610
|
dumped[project] << create_entry(entry)
|
516
611
|
end
|
612
|
+
else
|
613
|
+
@db.roots.each do |project|
|
614
|
+
dumped[project] = Array.new
|
615
|
+
@db[project].each do |entry|
|
616
|
+
dumped[project] << create_entry(entry)
|
617
|
+
end
|
618
|
+
end
|
517
619
|
end
|
620
|
+
dumped
|
518
621
|
end
|
519
|
-
dumped
|
520
622
|
end
|
521
|
-
end #}}}
|
522
623
|
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
624
|
+
def create project
|
625
|
+
project = project.to_s.strip
|
626
|
+
@db[project.to_s] ||= Array.new unless project.empty?
|
627
|
+
end
|
527
628
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
629
|
+
def create_entry hash = {}
|
630
|
+
entry = OrderedHash.new
|
631
|
+
%w[ in out log total ].each do |key|
|
632
|
+
entry[key] = hash.getopt key
|
633
|
+
end
|
634
|
+
entry
|
532
635
|
end
|
533
|
-
|
534
|
-
|
535
|
-
end #}}}
|
536
|
-
}
|
636
|
+
end
|
637
|
+
}
|
data/gemspec.rb
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification::new do |spec|
|
|
28
28
|
spec.add_dependency 'main', '>= 2.6.0'
|
29
29
|
spec.add_dependency 'systemu', '>= 1.2.0'
|
30
30
|
spec.add_dependency 'orderedhash', '>= 0.0.3'
|
31
|
-
spec.add_dependency 'attributes', '5.0.0'
|
31
|
+
spec.add_dependency 'attributes', '>= 5.0.0'
|
32
32
|
|
33
33
|
spec.extensions << "extconf.rb" if File::exists? "extconf.rb"
|
34
34
|
|
metadata
CHANGED
@@ -1,56 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: punch
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-12-06 00:00:00 -07:00
|
8
|
-
summary: punch
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: ara.t.howard@gmail.com
|
12
|
-
homepage: http://codeforpeople.com/lib/ruby/punch/
|
13
|
-
rubyforge_project:
|
14
|
-
description:
|
15
|
-
autorequire: punch
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: false
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: 0.0.2
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Ara T. Howard
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- gemspec.rb
|
35
|
-
- install.rb
|
36
|
-
- README
|
37
|
-
test_files: []
|
38
|
-
|
39
|
-
rdoc_options: []
|
40
|
-
|
41
|
-
extra_rdoc_files: []
|
42
|
-
|
43
|
-
executables:
|
44
|
-
- punch
|
45
|
-
extensions: []
|
46
|
-
|
47
|
-
requirements: []
|
8
|
+
autorequire: punch
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
48
11
|
|
12
|
+
date: 2008-03-29 00:00:00 -06:00
|
13
|
+
default_executable:
|
49
14
|
dependencies:
|
50
15
|
- !ruby/object:Gem::Dependency
|
51
16
|
name: main
|
52
17
|
version_requirement:
|
53
|
-
version_requirements: !ruby/object:Gem::
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
19
|
requirements:
|
55
20
|
- - ">="
|
56
21
|
- !ruby/object:Gem::Version
|
@@ -59,7 +24,7 @@ dependencies:
|
|
59
24
|
- !ruby/object:Gem::Dependency
|
60
25
|
name: systemu
|
61
26
|
version_requirement:
|
62
|
-
version_requirements: !ruby/object:Gem::
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
28
|
requirements:
|
64
29
|
- - ">="
|
65
30
|
- !ruby/object:Gem::Version
|
@@ -68,7 +33,7 @@ dependencies:
|
|
68
33
|
- !ruby/object:Gem::Dependency
|
69
34
|
name: orderedhash
|
70
35
|
version_requirement:
|
71
|
-
version_requirements: !ruby/object:Gem::
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
37
|
requirements:
|
73
38
|
- - ">="
|
74
39
|
- !ruby/object:Gem::Version
|
@@ -77,9 +42,51 @@ dependencies:
|
|
77
42
|
- !ruby/object:Gem::Dependency
|
78
43
|
name: attributes
|
79
44
|
version_requirement:
|
80
|
-
version_requirements: !ruby/object:Gem::
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
46
|
requirements:
|
82
|
-
- - "
|
47
|
+
- - ">="
|
83
48
|
- !ruby/object:Gem::Version
|
84
49
|
version: 5.0.0
|
85
50
|
version:
|
51
|
+
description:
|
52
|
+
email: ara.t.howard@gmail.com
|
53
|
+
executables:
|
54
|
+
- punch
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- bin
|
61
|
+
- bin/punch
|
62
|
+
- gemspec.rb
|
63
|
+
- install.rb
|
64
|
+
- README
|
65
|
+
has_rdoc: false
|
66
|
+
homepage: http://codeforpeople.com/lib/ruby/punch/
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.0.1
|
88
|
+
signing_key:
|
89
|
+
specification_version: 2
|
90
|
+
summary: punch
|
91
|
+
test_files: []
|
92
|
+
|