be9-punch 0.1.0

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