senv 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,527 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ require 'json'
4
+ require 'yaml'
5
+ require 'base64'
6
+ require 'securerandom'
7
+ require 'fileutils'
8
+ require 'pathname'
9
+ require 'set'
10
+ require 'openssl'
11
+ require 'uri'
12
+ require 'cgi'
13
+ require 'shellwords'
14
+ require 'tmpdir'
15
+ require 'tempfile'
16
+ require 'pp'
17
+ require 'open3'
18
+
19
+ #
20
+ module Senv
21
+ class Script
22
+ attr_accessor :source
23
+ attr_accessor :root
24
+ attr_accessor :env
25
+ attr_accessor :argv
26
+ attr_accessor :stdout
27
+ attr_accessor :stdin
28
+ attr_accessor :stderr
29
+ attr_accessor :help
30
+
31
+ def run!(env = ENV, argv = ARGV)
32
+ before!
33
+ init!(env, argv)
34
+ parse_command_line!
35
+ set_mode!
36
+ run_mode!
37
+ after!
38
+ end
39
+
40
+ def init!(env, argv)
41
+ @klass = self.class
42
+ @env = env.to_hash.dup
43
+ @argv = argv.map{|arg| arg.dup}
44
+ @stdout = $stdout.dup
45
+ @stdin = $stdin.dup
46
+ @stderr = $stderr.dup
47
+ @help = @klass.help || Util.unindent(DEFAULT_HELP)
48
+ end
49
+
50
+ def self.before(&block)
51
+ @before ||= []
52
+ @before << block if block
53
+ @before
54
+ end
55
+
56
+ def before!
57
+ self.class.before.each{|block| block.call}
58
+ end
59
+
60
+ def self.after(&block)
61
+ @after ||= []
62
+ @after << block if block
63
+ @after
64
+ end
65
+
66
+ def after!
67
+ self.class.after.each{|block| block.call}
68
+ end
69
+
70
+ DEFAULT_HELP = <<-__
71
+ NAME
72
+ #TODO
73
+
74
+ SYNOPSIS
75
+ #TODO
76
+
77
+ DESCRIPTION
78
+ #TODO
79
+
80
+ EXAMPLES
81
+ #TODO
82
+ __
83
+
84
+ def parse_command_line!
85
+ @options = Hash.new
86
+ @opts = Hash.new
87
+
88
+ argv = []
89
+ head = []
90
+ tail = []
91
+
92
+ %w[ :: -- ].each do |stop|
93
+ if((i = @argv.index(stop)))
94
+ head = @argv.slice(0 ... i)
95
+ tail = @argv.slice((i + 1) ... @argv.size)
96
+ @argv = head
97
+ break
98
+ end
99
+ end
100
+
101
+ @argv.each do |arg|
102
+ case
103
+ when arg =~ %r`^\s*:([^:\s]+)[=](.+)`
104
+ key = $1
105
+ val = $2
106
+ @options[key] = val
107
+ when arg =~ %r`^\s*(:+)(.+)`
108
+ switch = $1
109
+ key = $2
110
+ val = switch.size.odd?
111
+ @options[key] = val
112
+ else
113
+ argv.push(arg)
114
+ end
115
+ end
116
+
117
+ argv += tail
118
+
119
+ @argv.replace(argv)
120
+
121
+ @options.each do |key, val|
122
+ @opts[key.to_s.to_sym] = val
123
+ end
124
+
125
+ [@options, @opts]
126
+ end
127
+
128
+ def set_mode!
129
+ case
130
+ when respond_to?("run_#{ @argv[0] }")
131
+ @mode = @argv.shift
132
+ else
133
+ @mode = nil
134
+ end
135
+ end
136
+
137
+ def run_mode!
138
+ if @mode
139
+ return send("run_#{ @mode }")
140
+ else
141
+ if respond_to?(:run)
142
+ return send(:run)
143
+ end
144
+
145
+ if @argv.empty?
146
+ run_help
147
+ else
148
+ abort("#{ $0 } help")
149
+ end
150
+ end
151
+ end
152
+
153
+ def run_help
154
+ STDOUT.puts(@help)
155
+ end
156
+
157
+ def help!
158
+ run_help
159
+ abort
160
+ end
161
+
162
+ #
163
+ module Util
164
+ def unindent(arg)
165
+ string = arg.to_s.dup
166
+ margin = nil
167
+ string.each_line do |line|
168
+ next if line =~ %r/^\s*$/
169
+ margin = line[%r/^\s*/] and break
170
+ end
171
+ string.gsub!(%r/^#{ margin }/, "") if margin
172
+ margin ? string : nil
173
+ end
174
+
175
+ def esc(*args)
176
+ args.flatten.compact.map{|arg| Shellwords.escape(arg)}.join(' ')
177
+ end
178
+
179
+ def uuid
180
+ SecureRandom.uuid
181
+ end
182
+
183
+ def tmpname(*args)
184
+ opts = extract_options!(*args)
185
+
186
+ base = opts.fetch(:base){ uuid }.to_s.strip
187
+ ext = opts.fetch(:ext){ 'tmp' }.to_s.strip.sub(/^[.]+/, '')
188
+ basename = opts.fetch(:basename){ "#{ base }.#{ ext }" }
189
+
190
+ File.join(Dir.tmpdir, basename)
191
+ end
192
+
193
+ def tmpfile(*args, &block)
194
+ opts = extract_options!(args)
195
+
196
+ path = tmpname(opts)
197
+
198
+
199
+ tmp = open(path, 'w+')
200
+ tmp.binmode
201
+ tmp.sync = true
202
+
203
+ unless args.empty?
204
+ src = args.join
205
+ tmp.write(src)
206
+ tmp.flush
207
+ tmp.rewind
208
+ end
209
+
210
+ if block
211
+ begin
212
+ block.call(tmp)
213
+ ensure
214
+ FileUtils.rm_rf(path)
215
+ end
216
+ else
217
+ at_exit{ Kernel.system("rm -rf #{ esc(path) }") }
218
+ return tmp
219
+ end
220
+ end
221
+
222
+ def extract_options!(args)
223
+ unless args.is_a?(Array)
224
+ args = [args]
225
+ end
226
+
227
+ opts = args.last.is_a?(Hash) ? args.pop : {}
228
+
229
+ symbolize_keys!(opts)
230
+
231
+ return opts
232
+ end
233
+
234
+ def extract_options(args)
235
+ opts = extract_options!(args)
236
+
237
+ args.push(opts)
238
+
239
+ opts
240
+ end
241
+
242
+ def symbolize_keys!(hash)
243
+ hash.keys.each do |key|
244
+ if key.is_a?(String)
245
+ val = hash.delete(key)
246
+
247
+ if val.is_a?(Hash)
248
+ symbolize_keys!(val)
249
+ end
250
+
251
+ hash[key.to_s.gsub('-', '_').to_sym] = val
252
+ end
253
+ end
254
+
255
+ return hash
256
+ end
257
+
258
+ def symbolize_keys(hash)
259
+ symbolize_keys!(copy(hash))
260
+ end
261
+
262
+ def copy(object)
263
+ Marshal.load(Marshal.dump(object))
264
+ end
265
+
266
+ def debug!(arg)
267
+ if arg.is_a?(String)
268
+ warn "[DEBUG] #{ arg }"
269
+ else
270
+ warn "[DEBUG] >\n#{ arg.to_yaml rescue arg.pretty_inspect }"
271
+ end
272
+ end
273
+
274
+ def debug(arg)
275
+ debug!(arg) if debug?
276
+ end
277
+
278
+ def debug?
279
+ ENV['SCRIPT_DEBUG'] || ENV['DEBUG']
280
+ end
281
+
282
+ def sys!(*args, &block)
283
+ opts = extract_options!(args)
284
+
285
+ cmd = args
286
+
287
+ debug(:cmd => cmd)
288
+
289
+ open3 = (
290
+ block ||
291
+ opts[:stdin] ||
292
+ opts[:quiet] ||
293
+ opts[:capture]
294
+ )
295
+
296
+ if(open3)
297
+ stdin = opts[:stdin]
298
+ stdout = ''
299
+ stderr = ''
300
+ status = nil
301
+
302
+ Open3.popen3(*cmd) do |i, o, e, t|
303
+ ot = async_reader_thread_for(o, stdout)
304
+ et = async_reader_thread_for(e, stderr)
305
+
306
+ i.write(stdin) if stdin
307
+ i.close
308
+
309
+ ot.join
310
+ et.join
311
+
312
+ status = t.value
313
+ end
314
+
315
+ if status.exitstatus == 0
316
+ result = nil
317
+
318
+ if opts[:capture]
319
+ result = stdout.to_s.strip
320
+ else
321
+ if block
322
+ result = block.call(status, stdout, stderr)
323
+ else
324
+ result = [status, stdout, stderr]
325
+ end
326
+ end
327
+
328
+ return(result)
329
+ else
330
+ if opts[:capture]
331
+ abort("#{ [cmd].join(' ') } #=> #{ status.exitstatus }")
332
+ else
333
+ false
334
+ end
335
+ end
336
+ else
337
+ env = opts[:env] || {}
338
+ argv = [env, *cmd]
339
+ system(*argv) || abort("#{ [cmd].join(' ') } #=> #{ $?.exitstatus }")
340
+ return true
341
+ end
342
+ end
343
+
344
+ def sys(*args, &block)
345
+ begin
346
+ sys!(*args, &block)
347
+ rescue Object
348
+ false
349
+ end
350
+ end
351
+
352
+ def async_reader_thread_for(io, accum)
353
+ Thread.new(io, accum) do |i, a|
354
+ Thread.current.abort_on_exception = true
355
+
356
+ while true
357
+ buf = i.read(8192)
358
+
359
+ if buf
360
+ a << buf
361
+ else
362
+ break
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ def realpath(path)
369
+ Pathname.new(path.to_s).expand_path.realpath.to_s
370
+ end
371
+
372
+ def filelist(*args, &block)
373
+ accum = (block || proc{ Set.new }).call
374
+ raise ArgumentError.new('accum.class != Set') unless accum.is_a?(Set)
375
+
376
+ _ = args.last.is_a?(Hash) ? args.pop : {}
377
+
378
+ entries = args.flatten.compact.map{|arg| realpath("#{ arg }")}.uniq.sort
379
+
380
+ entries.each do |entry|
381
+ case
382
+ when test(?f, entry)
383
+ file = realpath(entry)
384
+ accum << file
385
+
386
+ when test(?d, entry)
387
+ glob = File.join(entry, '**/**')
388
+
389
+ Dir.glob(glob) do |_entry|
390
+ case
391
+ when test(?f, _entry)
392
+ filelist(_entry){ accum }
393
+ when test(?d, entry)
394
+ filelist(_entry){ accum }
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+ accum.to_a
401
+ end
402
+
403
+ def slug_for(*args, &block)
404
+ Slug.for(*args, &block)
405
+ end
406
+
407
+ extend Util
408
+ end
409
+
410
+ def self.utils(&block)
411
+ block ? Util.module_eval(&block) : Util
412
+ end
413
+
414
+ def utils
415
+ Util
416
+ end
417
+
418
+ def u
419
+ Util
420
+ end
421
+
422
+ #
423
+ class Slug < ::String
424
+ Join = '-'
425
+
426
+ def Slug.for(*args)
427
+ options = args.last.is_a?(Hash) ? args.pop : {}
428
+
429
+ join = (options[:join] || options['join'] || Join).to_s
430
+
431
+ string = args.flatten.compact.join(' ')
432
+
433
+ tokens = string.scan(%r`[^\s#{ join }]+`)
434
+
435
+ tokens.map! do |token|
436
+ token.gsub(%r`[^\p{L}/.]`, '').downcase
437
+ end
438
+
439
+ tokens.map! do |token|
440
+ token.gsub(%r`[/.]`, join * 2)
441
+ end
442
+
443
+ tokens.join(join)
444
+ end
445
+ end
446
+
447
+ #
448
+ def noop
449
+ ENV['SCRIPT_NOOP'] || ENV['NOOP']
450
+ end
451
+
452
+ alias_method :noop?, :noop
453
+
454
+ #
455
+ def self.help(*args)
456
+ @help ||= nil
457
+
458
+ unless args.empty?
459
+ @help = utils.unindent(args.join)
460
+ end
461
+
462
+ @help
463
+ end
464
+
465
+ def self.run(*args, &block)
466
+ modes =
467
+ if args.empty?
468
+ [nil]
469
+ else
470
+ args
471
+ end
472
+
473
+ modes.each do |mode|
474
+ method_name =
475
+ if mode
476
+ "run_#{ mode }"
477
+ else
478
+ "run"
479
+ end
480
+
481
+ define_method(method_name, &block)
482
+ end
483
+ end
484
+
485
+ #
486
+ def Script.klass_for(&block)
487
+ Class.new(Script) do |klass|
488
+ def klass.name; "Script::Klass__#{ SecureRandom.uuid.to_s.gsub('-', '_') }"; end
489
+ klass.class_eval(&block)
490
+ end
491
+ end
492
+
493
+ def self.run!(*args, &block)
494
+ STDOUT.sync = true
495
+ STDERR.sync = true
496
+
497
+ %w[ PIPE INT ].each{|signal| Signal.trap(signal, "EXIT")}
498
+
499
+ script = (
500
+ source =
501
+ if binding.respond_to?(:source_location)
502
+ File.expand_path(binding.source_location.first)
503
+ else
504
+ File.expand_path(eval('__FILE__', block.binding))
505
+ end
506
+
507
+ root = File.dirname(source)
508
+
509
+ klass = Script.klass_for(&block)
510
+
511
+ instance = klass.new
512
+
513
+ instance.source = source
514
+ instance.root = root
515
+
516
+ instance
517
+ )
518
+
519
+ script.run!(*args)
520
+ end
521
+ end
522
+ end
523
+
524
+ #
525
+ def Senv.script(*args, &block)
526
+ Senv::Script.run!(*args, &block)
527
+ end