be9-punch 0.1.0

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