pyrat-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/bin/punch +622 -0
  3. data/gemspec.rb +38 -0
  4. data/install.rb +214 -0
  5. metadata +95 -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
+
data/bin/punch ADDED
@@ -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
+ }
data/gemspec.rb ADDED
@@ -0,0 +1,38 @@
1
+ lib, version = File::basename(File::dirname(File::expand_path(__FILE__))).split %r/-/, 2
2
+
3
+ require 'rubygems'
4
+
5
+ Gem::Specification::new do |spec|
6
+ $VERBOSE = nil
7
+
8
+ shiteless = lambda do |list|
9
+ list.delete_if do |file|
10
+ file =~ %r/\.svn/ or
11
+ file =~ %r/\.tmp/
12
+ end
13
+ end
14
+
15
+ spec.name = lib
16
+ spec.version = version
17
+ spec.platform = Gem::Platform::RUBY
18
+ spec.summary = lib
19
+
20
+ spec.files = shiteless[Dir::glob("**/**")]
21
+ spec.executables = shiteless[Dir::glob("bin/*")].map{|exe| File::basename exe}
22
+
23
+ spec.require_path = "lib"
24
+ spec.autorequire = lib
25
+
26
+ spec.has_rdoc = File::exist? "doc"
27
+ spec.test_suite_file = "test/#{ lib }.rb" if File::directory? "test"
28
+ spec.add_dependency 'main', '>= 2.6.0'
29
+ spec.add_dependency 'systemu', '>= 1.2.0'
30
+ spec.add_dependency 'orderedhash', '>= 0.0.3'
31
+ spec.add_dependency 'attributes', '>= 5.0.0'
32
+
33
+ spec.extensions << "extconf.rb" if File::exists? "extconf.rb"
34
+
35
+ spec.author = "Ara T. Howard"
36
+ spec.email = "ara.t.howard@gmail.com"
37
+ spec.homepage = "http://codeforpeople.com/lib/ruby/#{ lib }/"
38
+ end
data/install.rb ADDED
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rbconfig'
3
+ require 'find'
4
+ require 'ftools'
5
+ require 'tempfile'
6
+ include Config
7
+
8
+ LIBDIR = "lib"
9
+ LIBDIR_MODE = 0644
10
+
11
+ BINDIR = "bin"
12
+ BINDIR_MODE = 0755
13
+
14
+
15
+ $srcdir = CONFIG["srcdir"]
16
+ $version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
17
+ $libdir = File.join(CONFIG["libdir"], "ruby", $version)
18
+ $archdir = File.join($libdir, CONFIG["arch"])
19
+ $site_libdir = $:.find {|x| x =~ /site_ruby$/}
20
+ $bindir = CONFIG["bindir"] || CONFIG['BINDIR']
21
+ $ruby_install_name = CONFIG['ruby_install_name'] || CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
22
+ $ruby_ext = CONFIG['EXEEXT'] || ''
23
+ $ruby = File.join($bindir, ($ruby_install_name + $ruby_ext))
24
+
25
+ if !$site_libdir
26
+ $site_libdir = File.join($libdir, "site_ruby")
27
+ elsif $site_libdir !~ %r/#{Regexp.quote($version)}/
28
+ $site_libdir = File.join($site_libdir, $version)
29
+ end
30
+
31
+ def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
32
+ #{{{
33
+ path = []
34
+ dir = []
35
+ Find.find(srcdir) do |f|
36
+ next unless FileTest.file?(f)
37
+ next if (f = f[srcdir.length+1..-1]) == nil
38
+ next if (/CVS$/ =~ File.dirname(f))
39
+ next if (/\.svn/ =~ File.dirname(f))
40
+ next if f =~ %r/\.lnk/
41
+ next if f =~ %r/\.svn/
42
+ next if f =~ %r/\.swp/
43
+ next if f =~ %r/\.svn/
44
+ path.push f
45
+ dir |= [File.dirname(f)]
46
+ end
47
+ for f in dir
48
+ next if f == "."
49
+ next if f == "CVS"
50
+ File::makedirs(File.join(destdir, f))
51
+ end
52
+ for f in path
53
+ next if (/\~$/ =~ f)
54
+ next if (/^\./ =~ File.basename(f))
55
+ unless bin
56
+ File::install(File.join(srcdir, f), File.join(destdir, f), mode, true)
57
+ else
58
+ from = File.join(srcdir, f)
59
+ to = File.join(destdir, f)
60
+ shebangify(from) do |sf|
61
+ $deferr.print from, " -> ", File::catname(from, to), "\n"
62
+ $deferr.printf "chmod %04o %s\n", mode, to
63
+ File::install(sf, to, mode, false)
64
+ end
65
+ end
66
+ end
67
+ #}}}
68
+ end
69
+ def shebangify f
70
+ #{{{
71
+ open(f) do |fd|
72
+ buf = fd.read 42
73
+ if buf =~ %r/^\s*#\s*!.*ruby/o
74
+ ftmp = Tempfile::new("#{ $$ }_#{ File::basename(f) }")
75
+ begin
76
+ fd.rewind
77
+ ftmp.puts "#!#{ $ruby }"
78
+ while((buf = fd.read(8192)))
79
+ ftmp.write buf
80
+ end
81
+ ftmp.close
82
+ yield ftmp.path
83
+ ensure
84
+ ftmp.close!
85
+ end
86
+ else
87
+ yield f
88
+ end
89
+ end
90
+ #}}}
91
+ end
92
+ def ARGV.switch
93
+ #{{{
94
+ return nil if self.empty?
95
+ arg = self.shift
96
+ return nil if arg == '--'
97
+ if arg =~ /^-(.)(.*)/
98
+ return arg if $1 == '-'
99
+ raise 'unknown switch "-"' if $2.index('-')
100
+ self.unshift "-#{$2}" if $2.size > 0
101
+ "-#{$1}"
102
+ else
103
+ self.unshift arg
104
+ nil
105
+ end
106
+ #}}}
107
+ end
108
+ def ARGV.req_arg
109
+ #{{{
110
+ self.shift || raise('missing argument')
111
+ #}}}
112
+ end
113
+ def linkify d, linked = []
114
+ #--{{{
115
+ if test ?d, d
116
+ versioned = Dir[ File::join(d, "*-[0-9].[0-9].[0-9].rb") ]
117
+ versioned.each do |v|
118
+ src, dst = v, v.gsub(%r/\-[\d\.]+\.rb$/, '.rb')
119
+ lnk = nil
120
+ begin
121
+ if test ?l, dst
122
+ lnk = "#{ dst }.lnk"
123
+ puts "#{ dst } -> #{ lnk }"
124
+ File::rename dst, lnk
125
+ end
126
+ unless test ?e, dst
127
+ puts "#{ src } -> #{ dst }"
128
+ File::copy src, dst
129
+ linked << dst
130
+ end
131
+ ensure
132
+ if lnk
133
+ at_exit do
134
+ puts "#{ lnk } -> #{ dst }"
135
+ File::rename lnk, dst
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ linked
142
+ #--}}}
143
+ end
144
+
145
+
146
+ #
147
+ # main program
148
+ #
149
+
150
+ libdir = $site_libdir
151
+ bindir = $bindir
152
+ no_linkify = false
153
+ linked = nil
154
+ help = false
155
+
156
+ usage = <<-usage
157
+ #{ File::basename $0 }
158
+ -d, --destdir <destdir>
159
+ -l, --libdir <libdir>
160
+ -b, --bindir <bindir>
161
+ -r, --ruby <ruby>
162
+ -n, --no_linkify
163
+ -s, --sudo
164
+ -h, --help
165
+ usage
166
+
167
+ begin
168
+ while switch = ARGV.switch
169
+ case switch
170
+ when '-d', '--destdir'
171
+ libdir = ARGV.req_arg
172
+ when '-l', '--libdir'
173
+ libdir = ARGV.req_arg
174
+ when '-b', '--bindir'
175
+ bindir = ARGV.req_arg
176
+ when '-r', '--ruby'
177
+ $ruby = ARGV.req_arg
178
+ when '-n', '--no_linkify'
179
+ no_linkify = true
180
+ when '-s', '--sudo'
181
+ sudo = 'sudo'
182
+ when '-h', '--help'
183
+ help = true
184
+ else
185
+ raise "unknown switch #{switch.dump}"
186
+ end
187
+ end
188
+ rescue
189
+ STDERR.puts $!.to_s
190
+ STDERR.puts usage
191
+ exit 1
192
+ end
193
+
194
+ if help
195
+ STDOUT.puts usage
196
+ exit
197
+ end
198
+
199
+ system "#{ sudo } #{ $ruby } pre-install.rb" if test(?s, 'pre-install.rb')
200
+
201
+ unless no_linkify
202
+ linked = linkify('lib') + linkify('bin')
203
+ end
204
+
205
+ system "#{ $ruby } extconf.rb && make && #{ sudo } make install" if test(?s, 'extconf.rb')
206
+
207
+ install_rb(LIBDIR, libdir, LIBDIR_MODE)
208
+ install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
209
+
210
+ if linked
211
+ linked.each{|path| File::rm_f path}
212
+ end
213
+
214
+ system "#{ sudo } #{ $ruby } post-install.rb" if test(?s, 'post-install.rb')
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pyrat-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-02-25 00:00:00 -08:00
13
+ default_executable:
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
+ description: Simple commmand line based timetracker in ruby.
56
+ email: info@simplyexcited.co.uk
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files: []
62
+
63
+ files:
64
+ - bin/punch
65
+ - install.rb
66
+ - gemspec.rb
67
+ - README
68
+ has_rdoc: false
69
+ homepage: http://codeforpeople.com/lib/ruby/punch/
70
+ post_install_message:
71
+ rdoc_options: []
72
+
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.2.0
91
+ signing_key:
92
+ specification_version: 2
93
+ summary: Punch timetracker
94
+ test_files: []
95
+