senv 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/a.rb ADDED
@@ -0,0 +1,92 @@
1
+ task :dist => [:dist_rb] do
2
+ Dir.chdir(This.dir)
3
+
4
+ version = This.version
5
+ name = This.name.downcase
6
+
7
+ platforms = %w[
8
+ linux-x86 linux-x86_64 osx
9
+ ]
10
+
11
+ Dir.glob("./dist/#{ name }-*").each{|entry| FileUtils.rm_rf(entry)}
12
+
13
+ spawn = proc{|cmd| system(cmd) or abort("#{ cmd } #=> #{ $? }")}
14
+
15
+ entrypoint = File.expand_path("./dist/#{ name }.sh")
16
+
17
+ platforms.each do |platform|
18
+ distdir = "./dist/#{ name }-#{ version }-#{ platform }"
19
+ FileUtils.mkdir_p(distdir)
20
+
21
+ libdir = File.join(distdir, 'lib')
22
+ FileUtils.mkdir_p(libdir)
23
+
24
+ appdir = File.join(libdir, 'app')
25
+ FileUtils.mkdir_p(appdir)
26
+
27
+ rubydir = File.join(libdir, 'ruby')
28
+ FileUtils.mkdir_p(rubydir)
29
+
30
+ FileUtils.cp('./dist/senv.rb', appdir)
31
+
32
+ Dir.chdir(rubydir) do
33
+ basename = "traveling-ruby-20141215-2.1.5-#{ platform }.tar.gz"
34
+ cmd = "curl -s -L -O --fail https://d6r77u77i8pq3.cloudfront.net/releases/#{ basename }"
35
+ spawn[cmd]
36
+ spawn["tar -xzf #{ basename }"]
37
+ FileUtils.rm(basename)
38
+ end
39
+
40
+ Dir.chdir(distdir) do
41
+ FileUtils.cp(entrypoint, name)
42
+ end
43
+ end
44
+
45
+ system('tree -L 4 dist 2>/dev/null') if STDOUT.tty?
46
+
47
+ Dir.chdir './dist' do
48
+ platforms.each do |platform|
49
+ dist = "#{ name }-#{ version }-#{ platform }"
50
+ spawn["tar cvfz #{ dist }.tgz ./#{ dist } >/dev/null 2>&1"]
51
+ FileUtils.rm_rf(dist)
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+
58
+
59
+ HELP = <<-____.tap{|s| s.gsub!(s[%r/^\s*/], '')}
60
+ VERSION
61
+ <%= Senv.version %>
62
+
63
+ TL;DR;
64
+
65
+ $ tree ./config/senvs/
66
+
67
+ config/senvs/
68
+ development.enc.rb
69
+ development.rb
70
+ development.yml
71
+ development.json
72
+ production.enc.rb
73
+ production.rb
74
+ production.yml
75
+ staging.json
76
+ staging.enc.rb
77
+ staging.rb
78
+ staging.yml
79
+ staging.json
80
+
81
+
82
+ ~> export SENV=production
83
+
84
+ ~> ./bin/senv run-something-in-production senv
85
+
86
+ ~> ./bin/senv @staging # show it
87
+
88
+ ~> ./bin/senv @staging run-something-in-staging-senv
89
+
90
+ ____
91
+
92
+ puts HELP
@@ -0,0 +1,259 @@
1
+ #! /usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ #
5
+ HELP = <<-____
6
+
7
+ NAME
8
+ senv - secure 12-factor environment variables
9
+
10
+ VERSION
11
+ <%= Senv.version %>
12
+
13
+ TL;DR;
14
+
15
+ # WIP ...
16
+
17
+ ____
18
+
19
+ #
20
+ require "pathname"
21
+ script_d = Pathname.new(__FILE__).realpath.dirname.to_s
22
+
23
+ unless defined?(Senv)
24
+ require File.expand_path("../lib/senv", script_d)
25
+ end
26
+
27
+ unless defined?(Senv::Script)
28
+ require File.expand_path("../lib/senv/script", script_d)
29
+ end
30
+
31
+ #
32
+ Senv.script do
33
+ before do
34
+ $handle_senv_alias = proc do
35
+ if ARGV.first =~ /^@/
36
+ ENV['SENV'] = ARGV.shift.sub(/^@/, '').strip
37
+ end
38
+ end.call
39
+
40
+ $handle_old_fashioned_cries_for_help = proc do
41
+ if ARGV.delete('-h') || ARGV.delete('--help')
42
+ ARGV.unshift('help')
43
+ end
44
+ end.call
45
+ end
46
+
47
+ #
48
+ run do
49
+ exec!
50
+ end
51
+
52
+ #
53
+ run 'help' do
54
+ show_help!
55
+ end
56
+
57
+ #
58
+ run 'exec' do
59
+ exec!
60
+ end
61
+
62
+ #
63
+ run 'init' do
64
+ require "shellwords"
65
+
66
+ load_senv!
67
+
68
+ Senv.environment.to_hash.each do |key, val|
69
+ if val
70
+ STDOUT.puts "export #{ Shellwords.escape(key) }=#{ Shellwords.escape(val) }"
71
+ else
72
+ STDOUT.puts "unset #{ Shellwords.escape(key) }"
73
+ end
74
+ end
75
+ end
76
+
77
+ #
78
+ run '.setup' do
79
+ dir = @argv.shift || Dir.pwd
80
+ key = @argv.shift || @options['key'] || SecureRandom.uuid
81
+
82
+ key.strip!
83
+
84
+ Dir.chdir(dir) do
85
+ if test(?d, '.senv')
86
+ abort "#{ dir }/.senv directory exists"
87
+ end
88
+
89
+ FileUtils.mkdir_p('.senv')
90
+
91
+ IO.binwrite('.senv/_key', "#{ key }\n")
92
+
93
+ Senv.key = key
94
+
95
+ Senv.write(
96
+ '.senv/all.rb',
97
+ u.unindent(
98
+ <<-____
99
+ ENV['A'] = 'one'
100
+ ENV['B'] = 'two'
101
+ ENV['C'] = 'three'
102
+ ____
103
+ )
104
+ )
105
+
106
+ %w[ development production ].each do |env|
107
+ Senv.write(
108
+ ".senv/#{ env }.rb",
109
+ u.unindent(
110
+ <<-____
111
+ Senv.load(:all)
112
+
113
+ ENV['B'] = 'two (via #{ env }.rb)'
114
+ ____
115
+ )
116
+ )
117
+
118
+ Senv.write(
119
+ ".senv/#{ env }.enc.rb",
120
+ u.unindent(
121
+ <<-____
122
+ Senv.load(:all)
123
+
124
+ ENV['C'] = 'three (via #{ env }.enc.rb)'
125
+ ____
126
+ )
127
+ )
128
+ end
129
+
130
+ puts "[SENV] setup #{ File.expand_path(dir) }"
131
+
132
+ Dir.glob('.senv/**/**').sort.each do |entry|
133
+ next unless test(?f, entry)
134
+ puts "- #{ entry }"
135
+ end
136
+ end
137
+ end
138
+
139
+ #
140
+ run '.inspect' do
141
+ load_senv!
142
+
143
+ if @argv.empty?
144
+ puts Senv.environment.inspect
145
+ else
146
+ env = {}
147
+ @argv.each do |name|
148
+ env[name] = Senv.environment[name]
149
+ end
150
+ puts env.inspect
151
+ end
152
+ end
153
+
154
+ #
155
+ run '.edit' do
156
+ input = @argv[0]
157
+ output = input
158
+ options = @opts
159
+
160
+ data =
161
+ if test(?s, input)
162
+ Senv.read(input, options)
163
+ else
164
+ ''
165
+ end
166
+
167
+ ext = File.basename(input).split('.').last
168
+ editor = @options['editor'] || ENV['EDITOR'] || 'vim'
169
+
170
+ u.tmpfile(:ext => ext) do |tmp|
171
+ tmp.write(data)
172
+ tmp.close
173
+
174
+ if(system "#{ editor } #{ tmp.path }")
175
+ data = IO.binread(tmp.path)
176
+ Senv.write(output, data, options)
177
+ end
178
+ end
179
+ end
180
+
181
+ #
182
+ run '.read' do
183
+ input = @argv[0] || '-'
184
+ output = @argv[1] || '-'
185
+ options = @options
186
+
187
+ data =
188
+ if input == '-'
189
+ Senv.read('/dev/stdin', options)
190
+ else
191
+ Senv.read(input, options)
192
+ end
193
+
194
+ if output == '-'
195
+ Senv.write('/dev/stdout', data, options)
196
+ else
197
+ Senv.write(output, data, options)
198
+ end
199
+ end
200
+
201
+ run '.write' do
202
+ output = @argv[0] || '-'
203
+ input = @argv[1] || '-'
204
+ options = @options
205
+
206
+ data =
207
+ if input == '-'
208
+ Senv.read('/dev/stdin')
209
+ else
210
+ Senv.read(input)
211
+ end
212
+
213
+ if output == '-'
214
+ Senv.write('/dev/stdout', data, options)
215
+ else
216
+ Senv.write(output, data, options)
217
+ end
218
+ end
219
+
220
+ run '.get' do
221
+ load_senv!
222
+
223
+ @argv.each do |name|
224
+ puts ENV[name]
225
+ end
226
+ end
227
+
228
+ #
229
+ def show_help!
230
+ help = ERB.new(u.unindent(HELP)).result(::TOPLEVEL_BINDING)
231
+ STDOUT.puts(help)
232
+ end
233
+
234
+ def load_senv!
235
+ if @options['debug']
236
+ ENV['SENV_DEBUG'] = 'true'
237
+ end
238
+
239
+ if @options['key']
240
+ ENV['SENV_KEY'] = @options['key']
241
+ end
242
+
243
+ begin
244
+ Senv.load(:force => @options['force'])
245
+ rescue Senv::Error => e
246
+ abort(e.message)
247
+ end
248
+ end
249
+
250
+ def exec!
251
+ load_senv!
252
+
253
+ if @argv.empty?
254
+ STDOUT.puts Senv.environment.to_hash.to_yaml
255
+ else
256
+ exec(*@argv)
257
+ end
258
+ end
259
+ end
Binary file
@@ -0,0 +1,1468 @@
1
+ #! /usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ #
5
+ HELP = <<-____
6
+
7
+ NAME
8
+ senv - secure 12-factor environment variables
9
+
10
+ VERSION
11
+ <%= Senv.version %>
12
+
13
+ TL;DR;
14
+
15
+ # WIP ...
16
+
17
+ ____
18
+
19
+ #
20
+ require "pathname"
21
+ script_d = Pathname.new(__FILE__).realpath.dirname.to_s
22
+
23
+ unless defined?(Senv)
24
+ require File.expand_path("../lib/senv", script_d)
25
+ end
26
+
27
+ unless defined?(Senv::Script)
28
+ require File.expand_path("../lib/senv/script", script_d)
29
+ end
30
+
31
+ #
32
+ Senv.script do
33
+ before do
34
+ $handle_senv_alias = proc do
35
+ if ARGV.first =~ /^@/
36
+ ENV['SENV'] = ARGV.shift.sub(/^@/, '').strip
37
+ end
38
+ end.call
39
+
40
+ $handle_old_fashioned_cries_for_help = proc do
41
+ if ARGV.delete('-h') || ARGV.delete('--help')
42
+ ARGV.unshift('help')
43
+ end
44
+ end.call
45
+ end
46
+
47
+ #
48
+ run do
49
+ exec!
50
+ end
51
+
52
+ #
53
+ run 'help' do
54
+ show_help!
55
+ end
56
+
57
+ #
58
+ run 'exec' do
59
+ exec!
60
+ end
61
+
62
+ #
63
+ run 'init' do
64
+ require "shellwords"
65
+
66
+ load_senv!
67
+
68
+ Senv.environment.to_hash.each do |key, val|
69
+ if val
70
+ STDOUT.puts "export #{ Shellwords.escape(key) }=#{ Shellwords.escape(val) }"
71
+ else
72
+ STDOUT.puts "unset #{ Shellwords.escape(key) }"
73
+ end
74
+ end
75
+ end
76
+
77
+ #
78
+ run '.setup' do
79
+ dir = @argv.shift || Dir.pwd
80
+ key = @argv.shift || @options['key'] || SecureRandom.uuid
81
+
82
+ key.strip!
83
+
84
+ Dir.chdir(dir) do
85
+ if test(?d, '.senv')
86
+ abort "#{ dir }/.senv directory exists"
87
+ end
88
+
89
+ FileUtils.mkdir_p('.senv')
90
+
91
+ IO.binwrite('.senv/_key', "#{ key }\n")
92
+
93
+ Senv.key = key
94
+
95
+ Senv.write(
96
+ '.senv/all.rb',
97
+ u.unindent(
98
+ <<-____
99
+ ENV['A'] = 'one'
100
+ ENV['B'] = 'two'
101
+ ENV['C'] = 'three'
102
+ ____
103
+ )
104
+ )
105
+
106
+ %w[ development production ].each do |env|
107
+ Senv.write(
108
+ ".senv/#{ env }.rb",
109
+ u.unindent(
110
+ <<-____
111
+ Senv.load(:all)
112
+
113
+ ENV['B'] = 'two (via #{ env }.rb)'
114
+ ____
115
+ )
116
+ )
117
+
118
+ Senv.write(
119
+ ".senv/#{ env }.enc.rb",
120
+ u.unindent(
121
+ <<-____
122
+ Senv.load(:all)
123
+
124
+ ENV['C'] = 'three (via #{ env }.enc.rb)'
125
+ ____
126
+ )
127
+ )
128
+ end
129
+
130
+ puts "[SENV] setup #{ File.expand_path(dir) }"
131
+
132
+ Dir.glob('.senv/**/**').sort.each do |entry|
133
+ next unless test(?f, entry)
134
+ puts "- #{ entry }"
135
+ end
136
+ end
137
+ end
138
+
139
+ #
140
+ run '.inspect' do
141
+ load_senv!
142
+
143
+ if @argv.empty?
144
+ puts Senv.environment.inspect
145
+ else
146
+ env = {}
147
+ @argv.each do |name|
148
+ env[name] = Senv.environment[name]
149
+ end
150
+ puts env.inspect
151
+ end
152
+ end
153
+
154
+ #
155
+ run '.edit' do
156
+ input = @argv[0]
157
+ output = input
158
+ options = @opts
159
+
160
+ data =
161
+ if test(?s, input)
162
+ Senv.read(input, options)
163
+ else
164
+ ''
165
+ end
166
+
167
+ ext = File.basename(input).split('.').last
168
+ editor = @options['editor'] || ENV['EDITOR'] || 'vim'
169
+
170
+ u.tmpfile(:ext => ext) do |tmp|
171
+ tmp.write(data)
172
+ tmp.close
173
+
174
+ if(system "#{ editor } #{ tmp.path }")
175
+ data = IO.binread(tmp.path)
176
+ Senv.write(output, data, options)
177
+ end
178
+ end
179
+ end
180
+
181
+ #
182
+ run '.read' do
183
+ input = @argv[0] || '-'
184
+ output = @argv[1] || '-'
185
+ options = @options
186
+
187
+ data =
188
+ if input == '-'
189
+ Senv.read('/dev/stdin', options)
190
+ else
191
+ Senv.read(input, options)
192
+ end
193
+
194
+ if output == '-'
195
+ Senv.write('/dev/stdout', data, options)
196
+ else
197
+ Senv.write(output, data, options)
198
+ end
199
+ end
200
+
201
+ run '.write' do
202
+ output = @argv[0] || '-'
203
+ input = @argv[1] || '-'
204
+ options = @options
205
+
206
+ data =
207
+ if input == '-'
208
+ Senv.read('/dev/stdin')
209
+ else
210
+ Senv.read(input)
211
+ end
212
+
213
+ if output == '-'
214
+ Senv.write('/dev/stdout', data, options)
215
+ else
216
+ Senv.write(output, data, options)
217
+ end
218
+ end
219
+
220
+ run '.get' do
221
+ load_senv!
222
+
223
+ @argv.each do |name|
224
+ puts ENV[name]
225
+ end
226
+ end
227
+
228
+ #
229
+ def show_help!
230
+ help = ERB.new(u.unindent(HELP)).result(::TOPLEVEL_BINDING)
231
+ STDOUT.puts(help)
232
+ end
233
+
234
+ def load_senv!
235
+ if @options['debug']
236
+ ENV['SENV_DEBUG'] = 'true'
237
+ end
238
+
239
+ if @options['key']
240
+ ENV['SENV_KEY'] = @options['key']
241
+ end
242
+
243
+ begin
244
+ Senv.load(:force => @options['force'])
245
+ rescue Senv::Error => e
246
+ abort(e.message)
247
+ end
248
+ end
249
+
250
+ def exec!
251
+ load_senv!
252
+
253
+ if @argv.empty?
254
+ STDOUT.puts Senv.environment.to_hash.to_yaml
255
+ else
256
+ exec(*@argv)
257
+ end
258
+ end
259
+ end
260
+
261
+
262
+ BEGIN {
263
+
264
+ ### <lib src='lib/senv.rb'>
265
+
266
+ #
267
+ require 'erb'
268
+ require 'yaml'
269
+ require 'json'
270
+ require 'rbconfig'
271
+ require 'pp'
272
+ require 'time'
273
+ require 'fileutils'
274
+ require 'pathname'
275
+ require 'thread'
276
+ require 'openssl'
277
+ require 'tmpdir'
278
+ require 'securerandom'
279
+
280
+ #
281
+ module Senv
282
+ #
283
+ VERSION = '0.4.2'.freeze
284
+
285
+ def Senv.version
286
+ VERSION
287
+ end
288
+
289
+ #
290
+ LICENSE = 'MIT'.freeze
291
+
292
+ def Senv.license
293
+ LICENSE
294
+ end
295
+
296
+ #
297
+ SUMMARY = ''
298
+
299
+ #
300
+ DEFAULT = 'development'.freeze
301
+
302
+ def Senv.default
303
+ DEFAULT
304
+ end
305
+
306
+ #
307
+ class Error < StandardError
308
+ end
309
+
310
+ def Senv.error!(*args, &block)
311
+ raise Error.new(*args, &block)
312
+ end
313
+
314
+ #
315
+ def Senv.env
316
+ ENV['SENV']
317
+ end
318
+
319
+ def Senv.env=(env)
320
+ if env
321
+ ENV['SENV'] = env.to_s.strip
322
+ else
323
+ ENV.delete('SENV')
324
+ end
325
+ end
326
+
327
+ #
328
+ def Senv.load(*args)
329
+ Senv.thread_safe do
330
+ #
331
+ env, options = Senv.parse_load_args(*args)
332
+
333
+ #
334
+ force = !!(options['force'] || options[:force])
335
+
336
+ #
337
+ a_parent_process_has_already_loaded_the_senv = (
338
+ ENV['SENV'] == env &&
339
+ ENV['SENV_LOADED'] &&
340
+ ENV['SENV_ENVIRONMENT']
341
+ )
342
+
343
+ if(a_parent_process_has_already_loaded_the_senv && !force)
344
+ Senv.env = env
345
+ Senv.loaded = JSON.parse(ENV['SENV_LOADED'])
346
+ Senv.environment = JSON.parse(ENV['SENV_ENVIRONMENT'])
347
+ return env
348
+ end
349
+
350
+ #
351
+ unless Senv.loading
352
+ loading = Senv.loading
353
+
354
+ begin
355
+ Senv.loading = env
356
+
357
+ Senv.loaded.clear
358
+ Senv.environment.clear
359
+
360
+ Senv.load_config_paths_for(env)
361
+
362
+ Senv.env = env
363
+
364
+ ENV['SENV'] = Senv.env
365
+ ENV['SENV_LOADED'] = JSON.generate(Senv.loaded)
366
+ ENV['SENV_ENVIRONMENT'] = JSON.generate(Senv.environment)
367
+
368
+ return env
369
+ ensure
370
+ Senv.loading = loading
371
+ end
372
+ else
373
+ a_config_file_imports_another_senv = (
374
+ Senv.loading != env
375
+ )
376
+
377
+ if a_config_file_imports_another_senv
378
+ Senv.load_config_paths_for(env)
379
+ return env
380
+ end
381
+
382
+ a_config_file_imports_itself_recursively = (
383
+ Senv.loading == env
384
+ )
385
+
386
+ if a_config_file_imports_itself_recursively
387
+ :cowardly_refuse_to_infinitely_recurse
388
+ return nil
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ def Senv.load!(*args)
395
+ env, options = Senv.parse_load_args(*args)
396
+ options['force'] = options[:force] = true
397
+ Senv.load(env, options)
398
+ end
399
+
400
+ def Senv.parse_load_args(*args)
401
+ env = Senv.env || Senv.default
402
+ options = Hash.new
403
+
404
+ case args.first
405
+ when String, Symbol
406
+ env = args.shift.to_s
407
+ end
408
+
409
+ case args.first
410
+ when Hash
411
+ options = args.shift
412
+ end
413
+
414
+ [env, options]
415
+ end
416
+
417
+ def Senv.thread_safe(&block)
418
+ THREAD_SAFE.synchronize(&block)
419
+ end
420
+ THREAD_SAFE = ::Monitor.new
421
+
422
+ def Senv.load_config_paths_for(env)
423
+ paths = Senv.config_paths_for(env)
424
+ Senv.load_config_paths(*paths)
425
+ end
426
+
427
+ def Senv.config_paths_for(env)
428
+ glob = "**/#{ env }.{rb,enc.rb}"
429
+
430
+ Senv.directory.glob(glob).sort_by do |path|
431
+ ext = path.basename.extname.split('.')
432
+ [path.basename.to_s.size, ext.size]
433
+ end
434
+ end
435
+
436
+ def Senv.load_config_paths(*paths)
437
+ libs = []
438
+ configs = []
439
+
440
+ paths.each do |path|
441
+ exts = path.extname.split('.')[1..-1]
442
+
443
+ case
444
+ when exts.include?('rb')
445
+ libs << path
446
+ else
447
+ configs << path
448
+ end
449
+ end
450
+
451
+ {
452
+ libs => :load_lib,
453
+ configs => :load_config,
454
+ }.each do |list, loader|
455
+ list.each do |path|
456
+ path = path.to_s
457
+
458
+ Senv.debug({'loading' => path})
459
+
460
+ if Senv.loaded.has_key?(path)
461
+ Senv.debug({'skipping' => path})
462
+ next
463
+ end
464
+
465
+ Senv.loaded[path] = nil
466
+
467
+ captured =
468
+ Senv.capturing_environment_changes do
469
+ Senv.send(loader, path)
470
+ end
471
+
472
+ changes = captured.changes
473
+
474
+ Senv.debug({path => changes})
475
+
476
+ Senv.loaded[path] = changes
477
+
478
+ captured.apply(Senv.environment)
479
+ end
480
+ end
481
+ end
482
+
483
+ #
484
+ def Senv.loading
485
+ @loading
486
+ end
487
+
488
+ def Senv.loading=(env)
489
+ @loading = env.to_s.strip
490
+ end
491
+
492
+ def Senv.loaded
493
+ @loaded ||= {}
494
+ end
495
+
496
+ def Senv.loaded=(hash)
497
+ @loaded = hash
498
+ end
499
+
500
+ def Senv.environment
501
+ @environment ||= {}
502
+ end
503
+
504
+ def Senv.environment=(hash)
505
+ @environment = hash
506
+ end
507
+
508
+ #
509
+ def Senv.realpath(path)
510
+ Pathname.new(path.to_s).realpath
511
+ end
512
+
513
+ def Senv.expand_path(path)
514
+ (realpath(path) rescue File.expand_path(path)).to_s
515
+ end
516
+
517
+ #
518
+ def Senv.root
519
+ determine_root! unless @root
520
+ @root
521
+ end
522
+
523
+ def Senv.root=(root)
524
+ @root = realpath(root)
525
+ end
526
+
527
+ def Senv.determine_root!
528
+ if ENV['SENV_ROOT']
529
+ Senv.root = ENV['SENV_ROOT']
530
+ return @root
531
+ else
532
+ Senv.search_path.each do |dirname|
533
+ if test(?d, dirname)
534
+ Senv.root = dirname
535
+ return @root
536
+ end
537
+ end
538
+ end
539
+
540
+ msg = "[SENV] no `.senv` directory found via `#{ Senv.search_path.join(' | ') }`"
541
+ Senv.error!(msg)
542
+ end
543
+
544
+ def Senv.directory
545
+ Senv.root.join('.senv')
546
+ end
547
+
548
+ def Senv.search_path
549
+ search_path = []
550
+
551
+ if ENV['SENV_PATH']
552
+ ENV['SENV_PATH'].split(':').each do |path|
553
+ search_path << Senv.expand_path(path).to_s
554
+ end
555
+ else
556
+ Pathname.pwd.realpath.ascend do |path|
557
+ search_path << path.to_s
558
+ end
559
+ end
560
+
561
+ search_path
562
+ end
563
+
564
+ def Senv.key_path
565
+ Senv.directory.join('_key')
566
+ end
567
+
568
+ def Senv.key
569
+ if ENV['SENV_KEY']
570
+ ENV['SENV_KEY']
571
+ else
572
+ if Senv.key_path.exist?
573
+ Senv.key_path.binread.strip
574
+ else
575
+ msg = "Senv.key not found in : #{ Senv.key_path }"
576
+ Senv.error!(msg)
577
+ end
578
+ end
579
+ end
580
+
581
+ def Senv.key=(key)
582
+ ENV['SENV_KEY'] = key.to_s.strip
583
+ end
584
+
585
+ def Senv.key_source
586
+ if ENV['SENV_KEY']
587
+ "ENV['SENV_KEY']"
588
+ else
589
+ Senv.key_path rescue '(no key source)'
590
+ end
591
+ end
592
+
593
+ #
594
+ ENCRYPTED_PATH_RE = Regexp.union(/\.enc(rypted)$/, /\.enc(rypted)?\./)
595
+
596
+ def Senv.is_encrypted?(path)
597
+ path.to_s =~ ENCRYPTED_PATH_RE
598
+ end
599
+
600
+ #
601
+ def Senv.binread(path, options = {})
602
+ data = IO.binread(path)
603
+
604
+ encrypted =
605
+ if options.has_key?('encrypted')
606
+ options['encrypted']
607
+ else
608
+ Senv.is_encrypted?(path)
609
+ end
610
+
611
+ if encrypted
612
+ data =
613
+ begin
614
+ Blowfish.decrypt(Senv.key, data)
615
+ rescue
616
+ abort "could not decrypt `#{ path }` with key `#{ Senv.key }` from `#{ Senv.key_source }`"
617
+ end
618
+ end
619
+
620
+ data
621
+ end
622
+
623
+ def Senv.read(path, options = {})
624
+ Senv.binread(path)
625
+ end
626
+
627
+ def Senv.binwrite(path, data, options = {})
628
+ encrypted =
629
+ if options.has_key?('encrypted')
630
+ options['encrypted']
631
+ else
632
+ Senv.is_encrypted?(path)
633
+ end
634
+
635
+ if encrypted
636
+ data =
637
+ begin
638
+ Blowfish.encrypt(Senv.key, data)
639
+ rescue
640
+ abort "could not encrypt `#{ data.to_s.split("\n").first }...` with key `#{ Senv.key }` from `#{ Senv.key_source }`"
641
+ end
642
+ end
643
+
644
+ IO.binwrite(path, data)
645
+ end
646
+
647
+ def Senv.write(path, data, options = {})
648
+ Senv.binwrite(path, data, options)
649
+ end
650
+
651
+ def Senv.load_lib(path)
652
+ #
653
+ code = Senv.binread(path.to_s)
654
+ binding = ::TOPLEVEL_BINDING
655
+ filename = path.to_s
656
+
657
+ #
658
+ Kernel.eval(code, binding, filename)
659
+ end
660
+
661
+ def Senv.load_config(path)
662
+ #
663
+ erb = Senv.binread(path)
664
+ expanded = ERB.new(erb).result(::TOPLEVEL_BINDING)
665
+ buf = expanded
666
+
667
+ #
668
+ encoded = buf
669
+
670
+ config =
671
+ case
672
+ when path =~ /yml|yaml/
673
+ YAML.load(encoded)
674
+ when path =~ /json/
675
+ JSON.parse(encoded)
676
+ else
677
+ abort "unknown config format in #{ path }"
678
+ end
679
+
680
+ #
681
+ unless config && config.is_a?(Hash)
682
+ abort "[SENV] failed to load #{ path }"
683
+ end
684
+
685
+ #
686
+ config.each do |key, val|
687
+ ENV[key.to_s] = val.to_s
688
+ end
689
+
690
+ #
691
+ config
692
+ end
693
+
694
+ #
695
+ def Senv.debug(*args, &block)
696
+ if args.empty? && block.nil?
697
+ return Senv.debug?
698
+ end
699
+
700
+ return nil unless Senv.debug?
701
+
702
+ lines = []
703
+
704
+ args.each do |arg|
705
+ case
706
+ when arg.is_a?(String)
707
+ lines << arg.strip
708
+ else
709
+ lines << arg.inspect.strip
710
+ end
711
+ end
712
+
713
+ return nil if(lines.empty? && block.nil?)
714
+
715
+ if lines
716
+ lines.each do |line|
717
+ STDERR.puts "# [SENV=#{ Senv.env }] : #{ line }"
718
+ end
719
+ end
720
+
721
+ if block
722
+ return block.call
723
+ else
724
+ true
725
+ end
726
+ end
727
+
728
+ def Senv.debug?
729
+ !!ENV['SENV_DEBUG']
730
+ end
731
+
732
+ def Senv.debug=(arg)
733
+ if arg
734
+ ENV['SENV_DEBUG'] = 'true'
735
+ else
736
+ ENV.delete('SENV_DEBUG')
737
+ end
738
+ end
739
+
740
+ #
741
+ def Senv.senvs
742
+ @senvs ||= Hash.new
743
+ end
744
+
745
+ def Senv.for_senv!(senv)
746
+ senv = senv.to_s
747
+
748
+ IO.popen('-', 'w+') do |io|
749
+ child = io.nil?
750
+
751
+ if child
752
+ Senv.load(senv)
753
+ puts Senv.environment.to_yaml
754
+ exit
755
+ else
756
+ YAML.load(io.read)
757
+ end
758
+ end
759
+ end
760
+
761
+ def Senv.for_senv(senv)
762
+ senv = senv.to_s
763
+
764
+ if Senv.senvs.has_key?(senv)
765
+ return Senv.senvs[senv]
766
+ end
767
+
768
+ Senv.senvs[senv] = Senv.for_senv!(senv)
769
+ end
770
+
771
+ def Senv.get(senv, var)
772
+ Senv.for_senv(senv)[var.to_s]
773
+ end
774
+
775
+ def Senv.get!(senv, var)
776
+ Senv.for_senv!(senv)[var.to_s]
777
+ end
778
+
779
+ #
780
+ module Blowfish
781
+ def cipher(senv, key, data)
782
+ cipher = OpenSSL::Cipher.new('bf-cbc').send(senv)
783
+ cipher.key = Digest::SHA256.digest(key.to_s).slice(0,16)
784
+ cipher.update(data) << cipher.final
785
+ end
786
+
787
+ def encrypt(key, data)
788
+ cipher(:encrypt, key, data)
789
+ end
790
+
791
+ def decrypt(key, text)
792
+ cipher(:decrypt, key, text)
793
+ end
794
+
795
+ def cycle(key, data)
796
+ decrypt(key, encrypt(key, data))
797
+ end
798
+
799
+ def recrypt(old_key, new_key, data)
800
+ encrypt(new_key, decrypt(old_key, data))
801
+ end
802
+
803
+ extend(self)
804
+ end
805
+
806
+ #
807
+ def Senv.capturing_environment_changes(&block)
808
+ EnvChangeTracker.track(&block)
809
+ end
810
+
811
+ class EnvChangeTracker < ::BasicObject
812
+ def initialize(env)
813
+ @env = env
814
+
815
+ @changes = {
816
+ :deleted => [],
817
+ :updated => [],
818
+ :created => [],
819
+ }
820
+
821
+ @change_for = proc do |key, val|
822
+ if @env.has_key?(key)
823
+ case
824
+ when val.nil?
825
+ {:type => :deleted, :info => [key, val]}
826
+ when val.to_s != @env[key].to_s
827
+ {:type => :updated, :info => [key, val]}
828
+ else
829
+ nil
830
+ end
831
+ else
832
+ {:type => :created, :info => [key, val]}
833
+ end
834
+ end
835
+
836
+ @track_change = proc do |key, val|
837
+ change = @change_for[key, val]
838
+
839
+ if change
840
+ @changes[change[:type]].push(change[:info])
841
+ end
842
+ end
843
+ end
844
+
845
+ def changes
846
+ @changes
847
+ end
848
+
849
+ def method_missing(method, *args, &block)
850
+ @env.send(method, *args, &block)
851
+ end
852
+
853
+ def []=(key, val)
854
+ @track_change[key, val]
855
+ @env[key] = val
856
+ end
857
+
858
+ def replace(hash)
859
+ hash.each do |key, val|
860
+ @track_change[key, val]
861
+ end
862
+ @env.replace(hash)
863
+ end
864
+
865
+ def store(key, val)
866
+ @track_change[key, val]
867
+ @env.store(key, val)
868
+ end
869
+
870
+ def delete(key)
871
+ @track_change[key, nil]
872
+ @env.delete(key)
873
+ end
874
+
875
+ def apply(env)
876
+ @changes[:created].each do |k, v|
877
+ env[k] = v
878
+ end
879
+ @changes[:updated].each do |k, v|
880
+ env[k] = v
881
+ end
882
+ @changes[:deleted].each do |k, v|
883
+ env.delete(k)
884
+ end
885
+ @changes
886
+ end
887
+
888
+ THREAD_SAFE = ::Monitor.new
889
+
890
+ def EnvChangeTracker.track(&block)
891
+ THREAD_SAFE.synchronize do
892
+ env = EnvChangeTracker.new(::ENV)
893
+
894
+ ::Object.send(:remove_const, :ENV)
895
+ ::Object.send(:const_set, :ENV, env)
896
+
897
+ begin
898
+ block.call
899
+ env
900
+ ensure
901
+ ::Object.send(:remove_const, :ENV)
902
+ ::Object.send(:const_set, :ENV, env)
903
+ end
904
+ end
905
+ end
906
+ end
907
+
908
+ #
909
+ class ::Pathname
910
+ unless ::Pathname.pwd.respond_to?(:glob)
911
+ def glob(glob, &block)
912
+ paths = []
913
+
914
+ Dir.glob("#{ self }/#{ glob }") do |entry|
915
+ path = Pathname.new(entry)
916
+
917
+ if block
918
+ block.call(path)
919
+ else
920
+ paths.push(path)
921
+ end
922
+ end
923
+
924
+ block ? nil : paths
925
+ end
926
+ end
927
+ end
928
+ end
929
+
930
+
931
+ ### </lib src='lib/senv.rb'>
932
+
933
+ ### <lib src='lib/senv/script.rb'>
934
+
935
+ #! /usr/bin/env ruby
936
+ #
937
+ require 'json'
938
+ require 'yaml'
939
+ require 'base64'
940
+ require 'securerandom'
941
+ require 'fileutils'
942
+ require 'pathname'
943
+ require 'set'
944
+ require 'openssl'
945
+ require 'uri'
946
+ require 'cgi'
947
+ require 'shellwords'
948
+ require 'tmpdir'
949
+ require 'tempfile'
950
+ require 'pp'
951
+ require 'open3'
952
+
953
+ #
954
+ module Senv
955
+ class Script
956
+ attr_accessor :source
957
+ attr_accessor :root
958
+ attr_accessor :env
959
+ attr_accessor :argv
960
+ attr_accessor :stdout
961
+ attr_accessor :stdin
962
+ attr_accessor :stderr
963
+ attr_accessor :help
964
+
965
+ def run!(env = ENV, argv = ARGV)
966
+ before!
967
+ init!(env, argv)
968
+ parse_command_line!
969
+ set_mode!
970
+ run_mode!
971
+ after!
972
+ end
973
+
974
+ def init!(env, argv)
975
+ @klass = self.class
976
+ @env = env.to_hash.dup
977
+ @argv = argv.map{|arg| arg.dup}
978
+ @stdout = $stdout.dup
979
+ @stdin = $stdin.dup
980
+ @stderr = $stderr.dup
981
+ @help = @klass.help || Util.unindent(DEFAULT_HELP)
982
+ end
983
+
984
+ def self.before(&block)
985
+ @before ||= []
986
+ @before << block if block
987
+ @before
988
+ end
989
+
990
+ def before!
991
+ self.class.before.each{|block| block.call}
992
+ end
993
+
994
+ def self.after(&block)
995
+ @after ||= []
996
+ @after << block if block
997
+ @after
998
+ end
999
+
1000
+ def after!
1001
+ self.class.after.each{|block| block.call}
1002
+ end
1003
+
1004
+ DEFAULT_HELP = <<-__
1005
+ NAME
1006
+ #TODO
1007
+
1008
+ SYNOPSIS
1009
+ #TODO
1010
+
1011
+ DESCRIPTION
1012
+ #TODO
1013
+
1014
+ EXAMPLES
1015
+ #TODO
1016
+ __
1017
+
1018
+ def parse_command_line!
1019
+ @options = Hash.new
1020
+ @opts = Hash.new
1021
+
1022
+ argv = []
1023
+ head = []
1024
+ tail = []
1025
+
1026
+ %w[ :: -- ].each do |stop|
1027
+ if((i = @argv.index(stop)))
1028
+ head = @argv.slice(0 ... i)
1029
+ tail = @argv.slice((i + 1) ... @argv.size)
1030
+ @argv = head
1031
+ break
1032
+ end
1033
+ end
1034
+
1035
+ @argv.each do |arg|
1036
+ case
1037
+ when arg =~ %r`^\s*:([^:\s]+)[=](.+)`
1038
+ key = $1
1039
+ val = $2
1040
+ @options[key] = val
1041
+ when arg =~ %r`^\s*(:+)(.+)`
1042
+ switch = $1
1043
+ key = $2
1044
+ val = switch.size.odd?
1045
+ @options[key] = val
1046
+ else
1047
+ argv.push(arg)
1048
+ end
1049
+ end
1050
+
1051
+ argv += tail
1052
+
1053
+ @argv.replace(argv)
1054
+
1055
+ @options.each do |key, val|
1056
+ @opts[key.to_s.to_sym] = val
1057
+ end
1058
+
1059
+ [@options, @opts]
1060
+ end
1061
+
1062
+ def set_mode!
1063
+ case
1064
+ when respond_to?("run_#{ @argv[0] }")
1065
+ @mode = @argv.shift
1066
+ else
1067
+ @mode = nil
1068
+ end
1069
+ end
1070
+
1071
+ def run_mode!
1072
+ if @mode
1073
+ return send("run_#{ @mode }")
1074
+ else
1075
+ if respond_to?(:run)
1076
+ return send(:run)
1077
+ end
1078
+
1079
+ if @argv.empty?
1080
+ run_help
1081
+ else
1082
+ abort("#{ $0 } help")
1083
+ end
1084
+ end
1085
+ end
1086
+
1087
+ def run_help
1088
+ STDOUT.puts(@help)
1089
+ end
1090
+
1091
+ def help!
1092
+ run_help
1093
+ abort
1094
+ end
1095
+
1096
+ #
1097
+ module Util
1098
+ def unindent(arg)
1099
+ string = arg.to_s.dup
1100
+ margin = nil
1101
+ string.each_line do |line|
1102
+ next if line =~ %r/^\s*$/
1103
+ margin = line[%r/^\s*/] and break
1104
+ end
1105
+ string.gsub!(%r/^#{ margin }/, "") if margin
1106
+ margin ? string : nil
1107
+ end
1108
+
1109
+ def esc(*args)
1110
+ args.flatten.compact.map{|arg| Shellwords.escape(arg)}.join(' ')
1111
+ end
1112
+
1113
+ def uuid
1114
+ SecureRandom.uuid
1115
+ end
1116
+
1117
+ def tmpname(*args)
1118
+ opts = extract_options!(*args)
1119
+
1120
+ base = opts.fetch(:base){ uuid }.to_s.strip
1121
+ ext = opts.fetch(:ext){ 'tmp' }.to_s.strip.sub(/^[.]+/, '')
1122
+ basename = opts.fetch(:basename){ "#{ base }.#{ ext }" }
1123
+
1124
+ File.join(Dir.tmpdir, basename)
1125
+ end
1126
+
1127
+ def tmpfile(*args, &block)
1128
+ opts = extract_options!(args)
1129
+
1130
+ path = tmpname(opts)
1131
+
1132
+
1133
+ tmp = open(path, 'w+')
1134
+ tmp.binmode
1135
+ tmp.sync = true
1136
+
1137
+ unless args.empty?
1138
+ src = args.join
1139
+ tmp.write(src)
1140
+ tmp.flush
1141
+ tmp.rewind
1142
+ end
1143
+
1144
+ if block
1145
+ begin
1146
+ block.call(tmp)
1147
+ ensure
1148
+ FileUtils.rm_rf(path)
1149
+ end
1150
+ else
1151
+ at_exit{ Kernel.system("rm -rf #{ esc(path) }") }
1152
+ return tmp
1153
+ end
1154
+ end
1155
+
1156
+ def extract_options!(args)
1157
+ unless args.is_a?(Array)
1158
+ args = [args]
1159
+ end
1160
+
1161
+ opts = args.last.is_a?(Hash) ? args.pop : {}
1162
+
1163
+ symbolize_keys!(opts)
1164
+
1165
+ return opts
1166
+ end
1167
+
1168
+ def extract_options(args)
1169
+ opts = extract_options!(args)
1170
+
1171
+ args.push(opts)
1172
+
1173
+ opts
1174
+ end
1175
+
1176
+ def symbolize_keys!(hash)
1177
+ hash.keys.each do |key|
1178
+ if key.is_a?(String)
1179
+ val = hash.delete(key)
1180
+
1181
+ if val.is_a?(Hash)
1182
+ symbolize_keys!(val)
1183
+ end
1184
+
1185
+ hash[key.to_s.gsub('-', '_').to_sym] = val
1186
+ end
1187
+ end
1188
+
1189
+ return hash
1190
+ end
1191
+
1192
+ def symbolize_keys(hash)
1193
+ symbolize_keys!(copy(hash))
1194
+ end
1195
+
1196
+ def copy(object)
1197
+ Marshal.load(Marshal.dump(object))
1198
+ end
1199
+
1200
+ def debug!(arg)
1201
+ if arg.is_a?(String)
1202
+ warn "[DEBUG] #{ arg }"
1203
+ else
1204
+ warn "[DEBUG] >\n#{ arg.to_yaml rescue arg.pretty_inspect }"
1205
+ end
1206
+ end
1207
+
1208
+ def debug(arg)
1209
+ debug!(arg) if debug?
1210
+ end
1211
+
1212
+ def debug?
1213
+ ENV['SCRIPT_DEBUG'] || ENV['DEBUG']
1214
+ end
1215
+
1216
+ def sys!(*args, &block)
1217
+ opts = extract_options!(args)
1218
+
1219
+ cmd = args
1220
+
1221
+ debug(:cmd => cmd)
1222
+
1223
+ open3 = (
1224
+ block ||
1225
+ opts[:stdin] ||
1226
+ opts[:quiet] ||
1227
+ opts[:capture]
1228
+ )
1229
+
1230
+ if(open3)
1231
+ stdin = opts[:stdin]
1232
+ stdout = ''
1233
+ stderr = ''
1234
+ status = nil
1235
+
1236
+ Open3.popen3(*cmd) do |i, o, e, t|
1237
+ ot = async_reader_thread_for(o, stdout)
1238
+ et = async_reader_thread_for(e, stderr)
1239
+
1240
+ i.write(stdin) if stdin
1241
+ i.close
1242
+
1243
+ ot.join
1244
+ et.join
1245
+
1246
+ status = t.value
1247
+ end
1248
+
1249
+ if status.exitstatus == 0
1250
+ result = nil
1251
+
1252
+ if opts[:capture]
1253
+ result = stdout.to_s.strip
1254
+ else
1255
+ if block
1256
+ result = block.call(status, stdout, stderr)
1257
+ else
1258
+ result = [status, stdout, stderr]
1259
+ end
1260
+ end
1261
+
1262
+ return(result)
1263
+ else
1264
+ if opts[:capture]
1265
+ abort("#{ [cmd].join(' ') } #=> #{ status.exitstatus }")
1266
+ else
1267
+ false
1268
+ end
1269
+ end
1270
+ else
1271
+ env = opts[:env] || {}
1272
+ argv = [env, *cmd]
1273
+ system(*argv) || abort("#{ [cmd].join(' ') } #=> #{ $?.exitstatus }")
1274
+ return true
1275
+ end
1276
+ end
1277
+
1278
+ def sys(*args, &block)
1279
+ begin
1280
+ sys!(*args, &block)
1281
+ rescue Object
1282
+ false
1283
+ end
1284
+ end
1285
+
1286
+ def async_reader_thread_for(io, accum)
1287
+ Thread.new(io, accum) do |i, a|
1288
+ Thread.current.abort_on_exception = true
1289
+
1290
+ while true
1291
+ buf = i.read(8192)
1292
+
1293
+ if buf
1294
+ a << buf
1295
+ else
1296
+ break
1297
+ end
1298
+ end
1299
+ end
1300
+ end
1301
+
1302
+ def realpath(path)
1303
+ Pathname.new(path.to_s).expand_path.realpath.to_s
1304
+ end
1305
+
1306
+ def filelist(*args, &block)
1307
+ accum = (block || proc{ Set.new }).call
1308
+ raise ArgumentError.new('accum.class != Set') unless accum.is_a?(Set)
1309
+
1310
+ _ = args.last.is_a?(Hash) ? args.pop : {}
1311
+
1312
+ entries = args.flatten.compact.map{|arg| realpath("#{ arg }")}.uniq.sort
1313
+
1314
+ entries.each do |entry|
1315
+ case
1316
+ when test(?f, entry)
1317
+ file = realpath(entry)
1318
+ accum << file
1319
+
1320
+ when test(?d, entry)
1321
+ glob = File.join(entry, '**/**')
1322
+
1323
+ Dir.glob(glob) do |_entry|
1324
+ case
1325
+ when test(?f, _entry)
1326
+ filelist(_entry){ accum }
1327
+ when test(?d, entry)
1328
+ filelist(_entry){ accum }
1329
+ end
1330
+ end
1331
+ end
1332
+ end
1333
+
1334
+ accum.to_a
1335
+ end
1336
+
1337
+ def slug_for(*args, &block)
1338
+ Slug.for(*args, &block)
1339
+ end
1340
+
1341
+ extend Util
1342
+ end
1343
+
1344
+ def self.utils(&block)
1345
+ block ? Util.module_eval(&block) : Util
1346
+ end
1347
+
1348
+ def utils
1349
+ Util
1350
+ end
1351
+
1352
+ def u
1353
+ Util
1354
+ end
1355
+
1356
+ #
1357
+ class Slug < ::String
1358
+ Join = '-'
1359
+
1360
+ def Slug.for(*args)
1361
+ options = args.last.is_a?(Hash) ? args.pop : {}
1362
+
1363
+ join = (options[:join] || options['join'] || Join).to_s
1364
+
1365
+ string = args.flatten.compact.join(' ')
1366
+
1367
+ tokens = string.scan(%r`[^\s#{ join }]+`)
1368
+
1369
+ tokens.map! do |token|
1370
+ token.gsub(%r`[^\p{L}/.]`, '').downcase
1371
+ end
1372
+
1373
+ tokens.map! do |token|
1374
+ token.gsub(%r`[/.]`, join * 2)
1375
+ end
1376
+
1377
+ tokens.join(join)
1378
+ end
1379
+ end
1380
+
1381
+ #
1382
+ def noop
1383
+ ENV['SCRIPT_NOOP'] || ENV['NOOP']
1384
+ end
1385
+
1386
+ alias_method :noop?, :noop
1387
+
1388
+ #
1389
+ def self.help(*args)
1390
+ @help ||= nil
1391
+
1392
+ unless args.empty?
1393
+ @help = utils.unindent(args.join)
1394
+ end
1395
+
1396
+ @help
1397
+ end
1398
+
1399
+ def self.run(*args, &block)
1400
+ modes =
1401
+ if args.empty?
1402
+ [nil]
1403
+ else
1404
+ args
1405
+ end
1406
+
1407
+ modes.each do |mode|
1408
+ method_name =
1409
+ if mode
1410
+ "run_#{ mode }"
1411
+ else
1412
+ "run"
1413
+ end
1414
+
1415
+ define_method(method_name, &block)
1416
+ end
1417
+ end
1418
+
1419
+ #
1420
+ def Script.klass_for(&block)
1421
+ Class.new(Script) do |klass|
1422
+ def klass.name; "Script::Klass__#{ SecureRandom.uuid.to_s.gsub('-', '_') }"; end
1423
+ klass.class_eval(&block)
1424
+ end
1425
+ end
1426
+
1427
+ def self.run!(*args, &block)
1428
+ STDOUT.sync = true
1429
+ STDERR.sync = true
1430
+
1431
+ %w[ PIPE INT ].each{|signal| Signal.trap(signal, "EXIT")}
1432
+
1433
+ script = (
1434
+ source =
1435
+ if binding.respond_to?(:source_location)
1436
+ File.expand_path(binding.source_location.first)
1437
+ else
1438
+ File.expand_path(eval('__FILE__', block.binding))
1439
+ end
1440
+
1441
+ root = File.dirname(source)
1442
+
1443
+ klass = Script.klass_for(&block)
1444
+
1445
+ instance = klass.new
1446
+
1447
+ instance.source = source
1448
+ instance.root = root
1449
+
1450
+ instance
1451
+ )
1452
+
1453
+ script.run!(*args)
1454
+ end
1455
+ end
1456
+ end
1457
+
1458
+ #
1459
+ def Senv.script(*args, &block)
1460
+ Senv::Script.run!(*args, &block)
1461
+ end
1462
+
1463
+
1464
+ ### </lib src='lib/senv/script.rb'>
1465
+
1466
+
1467
+
1468
+ }