punch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +27 -0
- data/bin/punch +536 -0
- data/gemspec.rb +38 -0
- data/install.rb +214 -0
- metadata +85 -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) [options]+
|
6
|
+
|
7
|
+
URIS
|
8
|
+
http://codeforpeople.com/lib/ruby/punch
|
9
|
+
http://rubyforge.org/projects/codeforpeople/
|
10
|
+
|
11
|
+
DESCRIPTION
|
12
|
+
punch is a k.i.s.s. tool for tracking the hours spent on various projects.
|
13
|
+
it supports logging hours under a project name, adding notes about work
|
14
|
+
done during that period, and several very simple reporting tools that
|
15
|
+
operate over a window of time.
|
16
|
+
|
17
|
+
run 'punch help modename' for more info.
|
18
|
+
|
19
|
+
PARAMETERS
|
20
|
+
--help, -h
|
21
|
+
|
22
|
+
EXAMPLES
|
23
|
+
. punch in projectname
|
24
|
+
. punch log projectname "can has time tracking"
|
25
|
+
. punch out projectname
|
26
|
+
. punch total projectname --after 2007-12-03 --before 2007-12-07
|
27
|
+
|
data/bin/punch
ADDED
@@ -0,0 +1,536 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
Main {
|
4
|
+
description <<-txt
|
5
|
+
punch is a k.i.s.s. tool for tracking the hours spent on various projects.
|
6
|
+
it supports logging hours under a project name, adding notes about work
|
7
|
+
done during that period, and several very simple reporting tools that
|
8
|
+
operate over a window of time.
|
9
|
+
|
10
|
+
run 'punch help modename' for more info.
|
11
|
+
txt
|
12
|
+
|
13
|
+
examples <<-txt
|
14
|
+
. punch in projectname
|
15
|
+
. punch log projectname "can has time tracking"
|
16
|
+
. punch out projectname
|
17
|
+
. punch total projectname --after 2007-12-03 --before 2007-12-07
|
18
|
+
txt
|
19
|
+
|
20
|
+
|
21
|
+
mode(:in){
|
22
|
+
description 'punch in on a project'
|
23
|
+
|
24
|
+
examples <<-txt
|
25
|
+
. punch in projectname
|
26
|
+
. punch in projectname --now 2007-01-01T09
|
27
|
+
. punch in projectname -n now
|
28
|
+
. punch in projectname -n 2007-01-01T09 --message 'new years day'
|
29
|
+
txt
|
30
|
+
|
31
|
+
mixin :argument_project, :option_db, :option_now, :option_message
|
32
|
+
|
33
|
+
run{ y db.punch(:in, project, 'now' => now, 'message' => message) }
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
mode(:out){
|
38
|
+
description 'punch out of a project'
|
39
|
+
|
40
|
+
examples <<-txt
|
41
|
+
. punch out projectname
|
42
|
+
. punch out projectname --now 2007-01-01T09
|
43
|
+
. punch out projectname -n now
|
44
|
+
. punch out projectname -n 2007-01-01T17 --message 'new years day'
|
45
|
+
. punch out projectname -n 2007-01-01T05pm -m 'new years day'
|
46
|
+
txt
|
47
|
+
|
48
|
+
mixin :argument_project, :option_db, :option_now, :option_message
|
49
|
+
|
50
|
+
run{ y db.punch(:out, project, 'now' => now, 'message' => message) }
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
mode(:log){
|
55
|
+
description 'log a message for a project you are currently logged into'
|
56
|
+
|
57
|
+
examples <<-txt
|
58
|
+
. punch log projectname 'bloody xml!'
|
59
|
+
. punch log projectname should have used yaml
|
60
|
+
txt
|
61
|
+
|
62
|
+
mixin :argument_project, :option_db, :argument_message
|
63
|
+
|
64
|
+
run{ y db.log(project, message) }
|
65
|
+
}
|
66
|
+
|
67
|
+
|
68
|
+
mode(:clock){
|
69
|
+
description 'punch in and out retroactively'
|
70
|
+
|
71
|
+
examples <<-txt
|
72
|
+
. punch clock projectname 2007-01-01T09am 2007-01-01T05pm -m "working on new year's day"
|
73
|
+
txt
|
74
|
+
|
75
|
+
mixin :argument_project, :argument_punch_in, :argument_punch_out, :option_db, :option_message
|
76
|
+
|
77
|
+
run{ y db.clock(project, punch_in, punch_out, 'message' => message) }
|
78
|
+
}
|
79
|
+
|
80
|
+
mode(:status){
|
81
|
+
description 'shows the status of named project, or all projects iff none given'
|
82
|
+
|
83
|
+
examples <<-txt
|
84
|
+
. punch status
|
85
|
+
. punch status projectname
|
86
|
+
txt
|
87
|
+
|
88
|
+
mixin :optional_project, :option_db
|
89
|
+
|
90
|
+
run{ y db.status(project) }
|
91
|
+
}
|
92
|
+
|
93
|
+
mode(:list){
|
94
|
+
description 'list a single/all projects, possibly filtering by time'
|
95
|
+
|
96
|
+
examples <<-txt
|
97
|
+
. punch list projectname
|
98
|
+
. punch list projectname --after 2007-01-01 --before 2007-01-31
|
99
|
+
. punch list
|
100
|
+
. punch list -A 2007-01-01 -B 2007-01-31
|
101
|
+
txt
|
102
|
+
|
103
|
+
mixin :optional_project, :option_after, :option_before, :option_db
|
104
|
+
|
105
|
+
run{ y db.list(project, 'window' => (after .. before)) }
|
106
|
+
}
|
107
|
+
|
108
|
+
mode(:total){
|
109
|
+
description 'total the time for a single/all projects, possibly filtering by time'
|
110
|
+
|
111
|
+
examples <<-txt
|
112
|
+
. punch total projectname
|
113
|
+
. punch total projectname --after 2007-01-01 --before 2007-01-31
|
114
|
+
. punch total
|
115
|
+
. punch total -A 2007-01-01 -B 2007-01-31
|
116
|
+
txt
|
117
|
+
|
118
|
+
mixin :optional_project, :option_after, :option_before, :option_db
|
119
|
+
|
120
|
+
run{ y db.total(project, 'window' => (after .. before)) }
|
121
|
+
}
|
122
|
+
|
123
|
+
mode(:delete){
|
124
|
+
description 'delete all records for a project'
|
125
|
+
|
126
|
+
examples <<-txt
|
127
|
+
. punch delete projectname
|
128
|
+
txt
|
129
|
+
|
130
|
+
mixin :argument_project, :option_db
|
131
|
+
|
132
|
+
run{
|
133
|
+
y db.delete(project)
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
mode(:dump){
|
138
|
+
description 'cat the yaml db with lock held'
|
139
|
+
|
140
|
+
examples <<-txt
|
141
|
+
. punch dump
|
142
|
+
. punch dump projectname
|
143
|
+
txt
|
144
|
+
|
145
|
+
mixin :optional_project, :option_db
|
146
|
+
|
147
|
+
run{ y db.dump(project) }
|
148
|
+
}
|
149
|
+
|
150
|
+
|
151
|
+
mixin :argument_project do
|
152
|
+
argument(:project){
|
153
|
+
attr
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
mixin :optional_project do
|
158
|
+
argument(:project){
|
159
|
+
optional
|
160
|
+
attr
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
mixin :argument_punch_in do
|
165
|
+
argument(:punch_in){
|
166
|
+
cast{|value| Time.parse value}
|
167
|
+
attr
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
mixin :argument_punch_out do
|
172
|
+
argument(:punch_out){
|
173
|
+
cast{|value| Time.parse value}
|
174
|
+
attr
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
mixin :argument_message do
|
179
|
+
argument(:message){
|
180
|
+
attr
|
181
|
+
arity -2
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
mixin :option_message do
|
186
|
+
option(:message, :m){
|
187
|
+
argument :required
|
188
|
+
attr
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
mixin :option_now do
|
193
|
+
option(:now){
|
194
|
+
optional
|
195
|
+
desc 'consider this time as the current time'
|
196
|
+
cast{|value| Time.parse value}
|
197
|
+
default Time::Now
|
198
|
+
attr
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
mixin :option_db do
|
203
|
+
option(:db){
|
204
|
+
argument :required
|
205
|
+
default File.join(Home, '.punch.yml')
|
206
|
+
attr{|param| DB.new(param.value) }
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
mixin :option_after do
|
211
|
+
option(:after, :A){
|
212
|
+
argument :required
|
213
|
+
desc 'limit data shown to entries after this iso8601 timestamp'
|
214
|
+
default Time::Beginning
|
215
|
+
cast :time
|
216
|
+
attr
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
mixin :option_before do
|
221
|
+
option(:before, :B){
|
222
|
+
argument :required
|
223
|
+
desc 'limit data shown to entries before this iso8601 timestamp'
|
224
|
+
default Time::End
|
225
|
+
cast :time
|
226
|
+
attr
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
run{ help! }
|
231
|
+
}
|
232
|
+
|
233
|
+
|
234
|
+
BEGIN {
|
235
|
+
require 'yaml'
|
236
|
+
require 'yaml/store'
|
237
|
+
require 'time'
|
238
|
+
require 'pathname'
|
239
|
+
|
240
|
+
begin
|
241
|
+
require 'rubygems'
|
242
|
+
gem 'main', '~> 2.6.0'
|
243
|
+
rescue
|
244
|
+
42
|
245
|
+
end
|
246
|
+
|
247
|
+
begin
|
248
|
+
require 'main'
|
249
|
+
rescue
|
250
|
+
abort "gem install main"
|
251
|
+
end
|
252
|
+
|
253
|
+
begin
|
254
|
+
require 'orderedhash'
|
255
|
+
rescue
|
256
|
+
abort "gem install orderedhash"
|
257
|
+
end
|
258
|
+
|
259
|
+
Home = File.expand_path(ENV['HOME'] || '~')
|
260
|
+
|
261
|
+
### hackity hack, don't talk back
|
262
|
+
|
263
|
+
class Object #{{{
|
264
|
+
def yaml_inline!
|
265
|
+
class << self
|
266
|
+
def to_yaml_style() :inline end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end #}}}
|
270
|
+
|
271
|
+
class Time #{{{
|
272
|
+
Beginning = Time.at(0).iso8601
|
273
|
+
|
274
|
+
End = Time.at((2**31)-1).iso8601
|
275
|
+
|
276
|
+
Now = Time.now.iso8601
|
277
|
+
|
278
|
+
Null = Time.at(0).instance_eval do
|
279
|
+
def to_s() "" end
|
280
|
+
def inspect() "" end
|
281
|
+
self
|
282
|
+
end
|
283
|
+
|
284
|
+
def to_s(n=0) iso8601(n) end
|
285
|
+
alias_method 'inspect', 'to_s'
|
286
|
+
|
287
|
+
### hack to fix Time.parse bug
|
288
|
+
Parse = Time.method 'parse'
|
289
|
+
def self.parse string
|
290
|
+
if string =~ %r'^\d\d\d\d-\d\d-\d\dT\d\d:?$'
|
291
|
+
string = string.sub(%r/:$/,'') + ':00'
|
292
|
+
end
|
293
|
+
Parse.call string
|
294
|
+
end
|
295
|
+
|
296
|
+
def to_yaml( opts = {} )
|
297
|
+
YAML::quick_emit( object_id, opts ) do |out|
|
298
|
+
tz = "Z"
|
299
|
+
# from the tidy Tobias Peters <t-peters@gmx.de> Thanks!
|
300
|
+
unless self.utc?
|
301
|
+
utc_same_instant = self.dup.utc
|
302
|
+
utc_same_writing = Time.utc(year,month,day,hour,min,sec,usec)
|
303
|
+
difference_to_utc = utc_same_writing - utc_same_instant
|
304
|
+
if (difference_to_utc < 0)
|
305
|
+
difference_sign = '-'
|
306
|
+
absolute_difference = -difference_to_utc
|
307
|
+
else
|
308
|
+
difference_sign = '+'
|
309
|
+
absolute_difference = difference_to_utc
|
310
|
+
end
|
311
|
+
difference_minutes = (absolute_difference/60).round
|
312
|
+
tz = "%s%02d:%02d" % [ difference_sign, difference_minutes / 60, difference_minutes % 60]
|
313
|
+
end
|
314
|
+
standard = self.strftime( "%Y-%m-%dT%H:%M:%S" )
|
315
|
+
standard += ".%02d" % [usec] #if usec.nonzero?
|
316
|
+
standard += "%s" % [tz]
|
317
|
+
if to_yaml_properties.empty?
|
318
|
+
out.scalar( taguri, standard, :plain )
|
319
|
+
else
|
320
|
+
out.map( taguri, to_yaml_style ) do |map|
|
321
|
+
map.add( 'at', standard )
|
322
|
+
to_yaml_properties.each do |m|
|
323
|
+
map.add( m, instance_variable_get( m ) )
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end #}}}
|
330
|
+
|
331
|
+
class Numeric #{{{
|
332
|
+
def hms s = self.to_i
|
333
|
+
h, s = s.divmod 3600
|
334
|
+
m, s = s.divmod 60
|
335
|
+
[h.to_i, m.to_i, s]
|
336
|
+
end
|
337
|
+
end #}}}
|
338
|
+
|
339
|
+
class Hash #{{{
|
340
|
+
def getopt key, default = nil
|
341
|
+
return self[key.to_s] if has_key?(key.to_s)
|
342
|
+
return self[key.to_s.to_sym] if has_key?(key.to_s.to_sym)
|
343
|
+
return self[key] if has_key?(key)
|
344
|
+
default
|
345
|
+
end
|
346
|
+
end #}}}
|
347
|
+
|
348
|
+
class DB #{{{
|
349
|
+
attr 'pathname'
|
350
|
+
attr 'db'
|
351
|
+
|
352
|
+
def initialize pathname #{{{
|
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
|
384
|
+
end
|
385
|
+
end #}}}
|
386
|
+
|
387
|
+
def log project, message, options = {} #{{{
|
388
|
+
@db.transaction do
|
389
|
+
create project
|
390
|
+
message = [message].flatten.compact.join(' ')
|
391
|
+
list = @db[project.to_s]
|
392
|
+
entry = list.last
|
393
|
+
if(entry.nil? or (entry and entry['out']))
|
394
|
+
abort 'you first need to punch in'
|
395
|
+
end
|
396
|
+
entry = create_entry entry
|
397
|
+
entry['log'] = [entry['log'], message].flatten.compact
|
398
|
+
list[-1] = entry
|
399
|
+
entry
|
400
|
+
end
|
401
|
+
end #}}}
|
402
|
+
|
403
|
+
def clock project, punch_in, punch_out, options = {} #{{{
|
404
|
+
@db.transaction do
|
405
|
+
log = [
|
406
|
+
"punch in @ #{ punch_in }",
|
407
|
+
options.getopt('message'),
|
408
|
+
"punch out @ #{ punch_out }",
|
409
|
+
].flatten.compact
|
410
|
+
create project
|
411
|
+
list = @db[project.to_s]
|
412
|
+
entry = create_entry('in' => punch_in, 'out' => punch_out, 'log' => log)
|
413
|
+
list << entry
|
414
|
+
@db[project.to_s] = list.sort_by{|e| e.values_at('in', 'out')}
|
415
|
+
entry
|
416
|
+
end
|
417
|
+
end #}}}
|
418
|
+
|
419
|
+
def list project, options = {} #{{{
|
420
|
+
window = options['window'] || options[:window]
|
421
|
+
@db.transaction do
|
422
|
+
create project
|
423
|
+
if project
|
424
|
+
@db[project.to_s].select do |entry|
|
425
|
+
a, b = entry.values_at 'in', 'out'
|
426
|
+
if a and b
|
427
|
+
window === Time.parse(a.to_s) and window === Time.parse(b.to_s)
|
428
|
+
end
|
429
|
+
end.map{|entry| create_entry entry}
|
430
|
+
else
|
431
|
+
@db.roots
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end #}}}
|
435
|
+
|
436
|
+
def status project, options = {} #{{{
|
437
|
+
@db.transaction do
|
438
|
+
h = Hash.new
|
439
|
+
@db.roots.each do |root|
|
440
|
+
entry = @db[root].last
|
441
|
+
a, b =
|
442
|
+
if entry
|
443
|
+
entry.values_at 'in', 'out'
|
444
|
+
else
|
445
|
+
[false, 'out']
|
446
|
+
end
|
447
|
+
#status = b ? "out @ #{ b }" : "in @ #{ a }"
|
448
|
+
status = b ? ['out', b] : ['in', a]
|
449
|
+
status.yaml_inline!
|
450
|
+
if project
|
451
|
+
h[project] = status if project == root
|
452
|
+
else
|
453
|
+
h[root] = status
|
454
|
+
end
|
455
|
+
end
|
456
|
+
h
|
457
|
+
end
|
458
|
+
end #}}}
|
459
|
+
|
460
|
+
def total project, options = {} #{{{
|
461
|
+
window = options['window'] || options[:window]
|
462
|
+
@db.transaction do
|
463
|
+
if project
|
464
|
+
create project
|
465
|
+
selected = @db[project.to_s].select do |entry|
|
466
|
+
a, b = entry.values_at 'in', 'out'
|
467
|
+
window === Time.parse(a.to_s) and window === Time.parse(b.to_s)
|
468
|
+
end
|
469
|
+
filtered = { project => selected }
|
470
|
+
else
|
471
|
+
filtered = Hash.new
|
472
|
+
@db.roots.each do |project|
|
473
|
+
selected = @db[project.to_s].select do |entry|
|
474
|
+
a, b = entry.values_at 'in', 'out'
|
475
|
+
window === Time.parse(a.to_s) and window === Time.parse(b.to_s)
|
476
|
+
end
|
477
|
+
filtered.update project => selected
|
478
|
+
end
|
479
|
+
end
|
480
|
+
total = Hash.new
|
481
|
+
filtered.each do |project, selected|
|
482
|
+
sum = 0
|
483
|
+
selected.each do |entry|
|
484
|
+
a, b = entry.values_at 'in', 'out'
|
485
|
+
next unless a and b
|
486
|
+
a = Time.parse(a.to_s)
|
487
|
+
b = Time.parse(b.to_s)
|
488
|
+
sum += (b - a)
|
489
|
+
end
|
490
|
+
total.update project => ('%0.2d:%0.2d:%0.2d' % sum.hms)
|
491
|
+
end
|
492
|
+
total
|
493
|
+
end
|
494
|
+
end #}}}
|
495
|
+
|
496
|
+
def delete project #{{{
|
497
|
+
@db.transaction do
|
498
|
+
@db.delete project
|
499
|
+
project
|
500
|
+
end
|
501
|
+
end #}}}
|
502
|
+
|
503
|
+
def dump project #{{{
|
504
|
+
@db.transaction do
|
505
|
+
dumped = OrderedHash.new
|
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|
|
513
|
+
dumped[project] = Array.new
|
514
|
+
@db[project].each do |entry|
|
515
|
+
dumped[project] << create_entry(entry)
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
dumped
|
520
|
+
end
|
521
|
+
end #}}}
|
522
|
+
|
523
|
+
def create project #{{{
|
524
|
+
project = project.to_s.strip
|
525
|
+
@db[project.to_s] ||= Array.new unless project.empty?
|
526
|
+
end #}}}
|
527
|
+
|
528
|
+
def create_entry hash = {} #{{{
|
529
|
+
entry = OrderedHash.new
|
530
|
+
%w[ in out log ].each do |key|
|
531
|
+
entry[key] = hash.getopt key
|
532
|
+
end
|
533
|
+
entry
|
534
|
+
end #}}}
|
535
|
+
end #}}}
|
536
|
+
}
|
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,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: punch
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
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:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Ara T. Howard
|
31
|
+
files:
|
32
|
+
- bin
|
33
|
+
- bin/punch
|
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: []
|
48
|
+
|
49
|
+
dependencies:
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: main
|
52
|
+
version_requirement:
|
53
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.6.0
|
58
|
+
version:
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: systemu
|
61
|
+
version_requirement:
|
62
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.2.0
|
67
|
+
version:
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: orderedhash
|
70
|
+
version_requirement:
|
71
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.3
|
76
|
+
version:
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: attributes
|
79
|
+
version_requirement:
|
80
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 5.0.0
|
85
|
+
version:
|