minvee 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +23 -0
  3. data/lib/minvee/serverbox.rb +2038 -0
  4. data/lib/minvee.rb +885 -0
  5. metadata +46 -0
data/lib/minvee.rb ADDED
@@ -0,0 +1,885 @@
1
+ #!/usr/bin/ruby -w
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ # requires
6
+ require 'fileutils'
7
+
8
+
9
+ #===============================================================================
10
+ # Minvee
11
+ # This module is just to initialize the namespace. It doesn't do anything.
12
+ #
13
+ module Minvee
14
+ # Version
15
+ VERSION = '0.0.1'
16
+ end
17
+ #
18
+ # Minvee
19
+ #===============================================================================
20
+
21
+
22
+
23
+ #===============================================================================
24
+ # Minvee::Client
25
+ #
26
+ module Minvee::Client
27
+ #---------------------------------------------------------------------------
28
+ # tmp dir
29
+ #
30
+ @@tmp_dir = '/tmp'
31
+
32
+ def self.tmp_dir
33
+ return @@tmp_dir
34
+ end
35
+
36
+ def self.tmp_dir=(p_dir)
37
+ @@tmp_dir = p_dir
38
+ end
39
+ #
40
+ # tmp dir
41
+ #---------------------------------------------------------------------------
42
+
43
+
44
+ #---------------------------------------------------------------------------
45
+ # process_command_line
46
+ #
47
+ def self.process_command_line(params)
48
+ # $tm.hrm
49
+
50
+ # action
51
+ command = params.shift
52
+
53
+ # must have action
54
+ if not command
55
+ puts 'syntax: ' + script_name() + ' command [command options]'
56
+ exit 1
57
+ end
58
+
59
+ # normalize command
60
+ command = command.downcase
61
+ command = command.gsub(/\s+/mu, '')
62
+
63
+ # run command
64
+ if cmd = @@COMMANDS[command]
65
+ puts cmd
66
+ else
67
+ puts 'unknown command: ' + command
68
+ end
69
+ end
70
+ #
71
+ # process_command_line
72
+ #---------------------------------------------------------------------------
73
+
74
+
75
+ # commands
76
+ @@COMMANDS = {}
77
+
78
+ #---------------------------------------------------------------------------
79
+ # prep
80
+ #
81
+ @@COMMANDS['prep'] = {}
82
+
83
+ @@COMMANDS['prep']['sub'] = Proc.new do
84
+ puts 'hello world'
85
+ end
86
+ #
87
+ # prep
88
+ #---------------------------------------------------------------------------
89
+
90
+
91
+ #---------------------------------------------------------------------------
92
+ # help
93
+ #
94
+ def self.help
95
+
96
+
97
+ puts 'syntax: ' + script_name() + ' command [command options]'
98
+
99
+ end
100
+ #
101
+ # help
102
+ #---------------------------------------------------------------------------
103
+
104
+
105
+ #---------------------------------------------------------------------------
106
+ # script_name
107
+ #
108
+ def self.script_name
109
+ return File.basename(__FILE__)
110
+ end
111
+ #
112
+ # script_name
113
+ #---------------------------------------------------------------------------
114
+
115
+
116
+ #---------------------------------------------------------------------------
117
+ # zip
118
+ #
119
+ def self.zip(root, opts={})
120
+ # $tm.hrm
121
+
122
+ # default options
123
+ opts = {'auto_delete'=>true}.merge(opts)
124
+
125
+ # get temp path to zip file
126
+ tgt_path = Minvee::Utils::TempPath.new(self.tmp_dir, 'ext'=>'tgz')
127
+
128
+ # build command
129
+ cmd = [
130
+ 'tar',
131
+ '-czf',
132
+ tgt_path.to_s,
133
+ './',
134
+ ]
135
+
136
+ # go to working copy and zip there
137
+ Dir.chdir(root) do
138
+ system(*cmd)
139
+ end
140
+
141
+ # return path
142
+ return tgt_path
143
+ end
144
+ #
145
+ # zip
146
+ #---------------------------------------------------------------------------
147
+
148
+
149
+ #---------------------------------------------------------------------------
150
+ # find_working_copy_dir
151
+ # TODO: Allow for possibility that effective user does not own the
152
+ # working copy.
153
+ #
154
+ def self.find_working_copy_dir(opts={})
155
+ # $tm.hrm
156
+
157
+ # use explicit or current directory
158
+ if current_dir = opts['dir']
159
+ current_dir = File.expand_path(current_dir, Dir::pwd)
160
+ else
161
+ current_dir = Dir::pwd
162
+ end
163
+
164
+ # loop while the current dir is owned by the effective user
165
+ while File.stat(current_dir).writable?
166
+ # check for minvee.json
167
+ if File.exist?(current_dir + '/minvee.json')
168
+ return current_dir
169
+ end
170
+
171
+ # go to parent directory
172
+ current_dir = File.expand_path('..', current_dir)
173
+ end
174
+
175
+ # didn't find working copy
176
+ return nil
177
+ end
178
+ #
179
+ # find_working_copy_dir
180
+ #---------------------------------------------------------------------------
181
+
182
+
183
+ #---------------------------------------------------------------------------
184
+ # find_working_copy
185
+ #
186
+ def self.find_working_copy(opts={})
187
+ # $tm.hrm
188
+
189
+ # get working copy directory
190
+ root = find_working_copy_dir(opts)
191
+ root or return nil
192
+
193
+ # instantiate working copy object
194
+ wc = Minvee::Client::WorkingCopy.new(root, opts)
195
+
196
+ # return
197
+ return wc
198
+ end
199
+ #
200
+ # find_working_copy
201
+ #---------------------------------------------------------------------------
202
+ end
203
+ #
204
+ # Minvee::Client
205
+ #===============================================================================
206
+
207
+
208
+ #===============================================================================
209
+ # Minvee::Utils
210
+ #
211
+ module Minvee::Utils
212
+ #---------------------------------------------------------------------------
213
+ # rand_pk
214
+ #
215
+ @@rnd_chars = 'a'.upto('z').to_a + '0'.upto('9').to_a
216
+ @@rnd_chars_max = @@rnd_chars.length - 1
217
+
218
+ def self.rand_pk()
219
+ # intialize rv
220
+ rv = ''
221
+
222
+ # build return string
223
+ 10.times do
224
+ rv += @@rnd_chars[rand 0 .. @@rnd_chars_max]
225
+ end
226
+
227
+ # freeze return value
228
+ rv.freeze
229
+
230
+ # return
231
+ return rv
232
+ end
233
+ #
234
+ # rand_pk
235
+ #---------------------------------------------------------------------------
236
+
237
+
238
+ #---------------------------------------------------------------------------
239
+ # atomic_write
240
+ #
241
+ def self.atomic_write(tgt_path)
242
+ # $tm.hrm
243
+
244
+ # get temp path
245
+ tmp_path = Minvee::Utils::TempPath.new(Minvee::Client.tmp_dir)
246
+
247
+ # ensure the tmp path gets deleted
248
+ begin
249
+ # provide write handle
250
+ File.open(tmp_path, 'w') do |tmp_file|
251
+ yield(tmp_file)
252
+ end
253
+
254
+ # move the temp file to the permanent location
255
+ FileUtils.mv(tmp_path, tgt_path)
256
+ ensure
257
+ # delete the tmp path
258
+ tmp_path.close
259
+ end
260
+ end
261
+ #
262
+ # atomic_write
263
+ #---------------------------------------------------------------------------
264
+ end
265
+ #
266
+ # Minvee::Utils
267
+ #===============================================================================
268
+
269
+
270
+ #===============================================================================
271
+ # Minvee::Client::Message
272
+ #
273
+ class Minvee::Client::Message
274
+ attr_reader :languages
275
+ attr_reader :params
276
+ attr_reader :path
277
+
278
+ #---------------------------------------------------------------------------
279
+ # initialize
280
+ #
281
+ def initialize(p_path)
282
+ # initialize params
283
+ @params = {}
284
+
285
+ # initialize languages
286
+ @languages = ['general']
287
+
288
+ # clone path
289
+ @path = p_path.clone
290
+ end
291
+ #
292
+ # initialize
293
+ #---------------------------------------------------------------------------
294
+
295
+
296
+ #---------------------------------------------------------------------------
297
+ # src
298
+ #
299
+ def src
300
+ # intialize search
301
+ search = @path.clone
302
+
303
+ # initialize rv
304
+ rv = Minvee::Client::Message::Sources.srcs['general']
305
+
306
+ # get message source
307
+ while search.length > 0
308
+ rv = rv[search.shift()]
309
+ end
310
+
311
+ # return
312
+ return rv
313
+ end
314
+ #
315
+ # src
316
+ #---------------------------------------------------------------------------
317
+
318
+
319
+ #---------------------------------------------------------------------------
320
+ # expand
321
+ #
322
+ def expand
323
+ # $tm.hrm
324
+
325
+ # initialize return value
326
+ rv = []
327
+
328
+ # split src
329
+ tokens = src.split(/(\[\[[a-z0-9\-\_]+?\]\])/imu)
330
+ tokens = tokens.grep(/\S/imu)
331
+
332
+ # build return value
333
+ tokens.each do |token|
334
+ if token.match(/\A\[\[[a-z0-9\-\_]+?\]\]\z/imu)
335
+ token.sub!(/\A\[\[\s*/imu, '')
336
+ token.sub!(/\s*\]\]\z/imu, '')
337
+
338
+ if @params.has_key?(token)
339
+ rv.push @params[token].to_s
340
+ end
341
+ else
342
+ rv.push token
343
+ end
344
+ end
345
+
346
+ # return
347
+ return rv.join('')
348
+ end
349
+ #
350
+ # expand
351
+ #---------------------------------------------------------------------------
352
+ end
353
+ #
354
+ # Minvee::Client::Message
355
+ #===============================================================================
356
+
357
+
358
+ #===============================================================================
359
+ # Minvee::Client::Message::Sources
360
+ #
361
+ module Minvee::Client::Message::Sources
362
+ @@srcs = {}
363
+ def self.srcs; return @@srcs; end;
364
+
365
+ # general
366
+ @@srcs['general'] = {
367
+ 'test' => {
368
+ 'a' => 'test message a',
369
+ 'b' => 'test [[[[my-param]] b',
370
+ },
371
+
372
+ 'questions' => {
373
+ 'yes' => {
374
+ 'letter' => 'y',
375
+ 'word' => 'yes',
376
+ },
377
+ 'no' => {
378
+ 'letter' => 'n',
379
+ 'word' => 'no',
380
+ },
381
+ }
382
+ }
383
+ end
384
+ #
385
+ # Minvee::Client::Message::Sources
386
+ #===============================================================================
387
+
388
+
389
+ #===============================================================================
390
+ # Minvee::Client::WorkingCopy
391
+ #
392
+ class Minvee::Client::WorkingCopy
393
+ attr_accessor :root
394
+ attr_reader :settings
395
+
396
+
397
+ #---------------------------------------------------------------------------
398
+ # initialize
399
+ #
400
+ def initialize(p_root, opts={})
401
+ # $tm.hrm
402
+
403
+ # p_root must be defined
404
+ if p_root.nil?
405
+ raise 'root-not-defined: did not get a defined root for working copy'
406
+ end
407
+
408
+ # hold on to root
409
+ @root = p_root.to_s.dup
410
+
411
+ # normalize and freeze root
412
+ @root.sub!(/\/+\z/imu, '')
413
+ @root.freeze
414
+
415
+ # if directory doesn't exist
416
+ if not File.exist?(@root)
417
+ raise 'root-not-exists: do not find a directory at ' + @root
418
+ end
419
+
420
+ # if settings file doesn't exist
421
+ if not File.exist?(self.settings_path)
422
+ raise 'no-settings-file: do not find minvee.json at ' + settings_path
423
+ end
424
+
425
+ # slurp in file
426
+ # KLUDGE: File or JSON will throw errors at this point if there are any.
427
+ # We'll just let those bubble up to the script that is calling this
428
+ # method instead of catching the exceptions here.
429
+ @settings = JSON.parse(File.read(self.settings_path))
430
+
431
+ # check settings
432
+ check_settings(opts)
433
+ end
434
+ #
435
+ # initialize
436
+ #---------------------------------------------------------------------------
437
+
438
+
439
+ #---------------------------------------------------------------------------
440
+ # zip
441
+ #
442
+ def zip(opts={})
443
+ return Minvee::Client.zip(@root, opts)
444
+ end
445
+ #
446
+ # zip
447
+ #---------------------------------------------------------------------------
448
+
449
+
450
+ #---------------------------------------------------------------------------
451
+ # settings_path
452
+ #
453
+ def settings_path
454
+ return @root + '/minvee.json'
455
+ end
456
+ #
457
+ # settings_path
458
+ #---------------------------------------------------------------------------
459
+
460
+
461
+ #---------------------------------------------------------------------------
462
+ # save_settings
463
+ #
464
+ def save_settings
465
+ Minvee::Utils.atomic_write(self.settings_path) do |file|
466
+ file.print JSON.pretty_generate(@settings)
467
+ end
468
+ end
469
+ #
470
+ # save_settings
471
+ #---------------------------------------------------------------------------
472
+
473
+
474
+ # private from here on
475
+ private
476
+
477
+
478
+ #---------------------------------------------------------------------------
479
+ # check_settings
480
+ # settings must have a project element, which is a hash, and that hash
481
+ # must have a url.
482
+ #
483
+ def check_settings(opts={})
484
+ # $tm.hrm
485
+
486
+ # default options
487
+ opts = {'check_url'=>true}.merge(opts)
488
+
489
+ # must be hash
490
+ if not @settings.is_a?(Hash)
491
+ raise "settings-not-hash: the settings for #{@root} are not a hash"
492
+ end
493
+
494
+ # convenience
495
+ project = @settings['project']
496
+
497
+ # must have project element
498
+ if not project
499
+ raise "settings-no-project: there are no project settings for #{@root}"
500
+ end
501
+
502
+ # project element must be a hash
503
+ if not project.is_a?(Hash)
504
+ raise "settings-project-not-hash: the project element for #{@root} is not a hash"
505
+ end
506
+
507
+ # must have project url
508
+ if not project['url']
509
+ raise "settings-project-no-url: the settings for #{@root} do not have a project url"
510
+ end
511
+
512
+ # must be an HTTPS url
513
+ # TODO: We'll probably need to expand what types of URLs are allowed.
514
+ # For example, we should probably allow http on localhost.
515
+ if opts['check_url']
516
+ if not URI(project['url']).is_a?(URI::HTTPS)
517
+ raise "settings-project-invalid-url: the project url for #{@root} is not an HTTPS url: " + project['url']
518
+ end
519
+ end
520
+ end
521
+ #
522
+ # check_settings
523
+ #---------------------------------------------------------------------------
524
+ end
525
+ #
526
+ # Minvee::Client::WorkingCopy
527
+ #===============================================================================
528
+
529
+
530
+ #===============================================================================
531
+ # Minvee::Utils::TempPath
532
+ #
533
+ class Minvee::Utils::TempPath
534
+ attr_reader :path
535
+ attr_accessor :auto_delete
536
+
537
+ #---------------------------------------------------------------------------
538
+ # initialize
539
+ #
540
+ def initialize(base, opts={})
541
+ # default options
542
+ opts = {'auto_delete'=>true}.merge(opts)
543
+
544
+ # if explicit path sent
545
+ if opts['explicit']
546
+ @path = base
547
+
548
+ # else build random path
549
+ else
550
+ # if path is a directory, add /
551
+ if File.directory?(base)
552
+ base = base.sub(/\/*\z/imu, '/')
553
+ end
554
+
555
+ # initialize path
556
+ @path = base + Minvee::Utils.rand_pk
557
+
558
+ # add suffix
559
+ if opts['suffix']
560
+ @path += '.' + opts['suffix']
561
+ end
562
+
563
+ # add ext if sent
564
+ if opts['ext']
565
+ ext = opts['ext'].dup
566
+ ext.sub!(/\A\.*/imu, '.')
567
+ @path += ext
568
+ end
569
+ end
570
+
571
+ # set finalizer for this object
572
+ ObjectSpace.define_finalizer(self, proc { self.class.finalize(self.object_id) })
573
+
574
+ # set auto delete
575
+ @auto_delete = opts['auto_delete']
576
+ end
577
+ #
578
+ # initialize
579
+ #---------------------------------------------------------------------------
580
+
581
+
582
+ #---------------------------------------------------------------------------
583
+ # to_s. and to_str
584
+ #
585
+ def to_str
586
+ return @path
587
+ end
588
+
589
+ def to_s
590
+ return @path
591
+ end
592
+
593
+ def +(str)
594
+ return @path.to_s + str
595
+ end
596
+ #
597
+ # to_s. and to_str
598
+ #---------------------------------------------------------------------------
599
+
600
+
601
+ #---------------------------------------------------------------------------
602
+ # close
603
+ #
604
+ def close
605
+ # if file exists, delete it
606
+ if self.auto_delete
607
+ if File.directory?(self.path)
608
+ FileUtils.rm_rf(self.path)
609
+ elsif File.exist?(self.path)
610
+ File.delete(self.path)
611
+ end
612
+ end
613
+ end
614
+ #
615
+ # close
616
+ #---------------------------------------------------------------------------
617
+
618
+
619
+ #---------------------------------------------------------------------------
620
+ # finalize method
621
+ #
622
+ def self.finalize(id)
623
+ # $tm.hrm
624
+
625
+ # get object from id
626
+ myself = ObjectSpace._id2ref(id)
627
+
628
+ # close
629
+ myself.close()
630
+ end
631
+ #
632
+ # finalize method
633
+ #---------------------------------------------------------------------------
634
+ end
635
+ #
636
+ # Minvee::Utils::TempPath
637
+ #===============================================================================
638
+
639
+
640
+ #===============================================================================
641
+ # Minvee::Utils::TempDir
642
+ #
643
+ class Minvee::Utils::TempDir < Minvee::Utils::TempPath
644
+ #---------------------------------------------------------------------------
645
+ # initialize
646
+ #
647
+ def initialize(base, opts={})
648
+ # call super method
649
+ super(base, opts)
650
+
651
+ # create directory
652
+ FileUtils::mkdir_p(@path)
653
+ end
654
+ #
655
+ # initialize
656
+ #---------------------------------------------------------------------------
657
+ end
658
+ #
659
+ # Minvee::Utils::TempDir
660
+ #===============================================================================
661
+
662
+
663
+ #===============================================================================
664
+ # Minvee::Client::Questions
665
+ #
666
+ module Minvee::Client::Questions
667
+ #---------------------------------------------------------------------------
668
+ # ask
669
+ #
670
+ def self.ask(type, opts)
671
+ # $tm.hrm
672
+
673
+ # select
674
+ if type == 'select'
675
+ return self.select(opts)
676
+
677
+ # short
678
+ elsif type == 'short'
679
+ return self.short(opts)
680
+
681
+ # boolean
682
+ elsif type == 'boolean'
683
+ return self.boolean(opts)
684
+
685
+ # else unknown
686
+ else
687
+ raise 'unknown question type: ' + type
688
+ end
689
+ end
690
+ #
691
+ # ask
692
+ #---------------------------------------------------------------------------
693
+
694
+
695
+ #---------------------------------------------------------------------------
696
+ # select
697
+ #
698
+ def self.select(opts)
699
+ # $tm.hrm
700
+
701
+ # initialize choice
702
+ choice = ''
703
+
704
+ # initialize choices hash
705
+ choices = {}
706
+
707
+ # initialize return hash
708
+ rv = {}
709
+
710
+ # normalized choices
711
+ opts['choices'].each do |k, v|
712
+ choices[k.downcase] = v
713
+ end
714
+
715
+ # prompt user until they make a choice
716
+ while not choices[choice]
717
+ # empty line
718
+ puts
719
+
720
+ # output question
721
+ puts opts['question']
722
+
723
+ # output choices
724
+ choices.each do |k, v|
725
+ puts k + ': ' + v['text']
726
+ end
727
+
728
+ # empty line
729
+ puts
730
+
731
+ # prompt
732
+ print opts['prompt'] + ': '
733
+
734
+ # get input from user
735
+ choice = gets
736
+ choice.sub!(/\s+\z/imu, '')
737
+ choice.sub!(/\A\s+/imu, '')
738
+ choice.downcase!
739
+ end
740
+
741
+ # store choice
742
+ rv['choice'] = choice
743
+
744
+ # if "other"
745
+ if choices[choice]['other']
746
+ while not rv['other']
747
+ # get other choice
748
+ print choices[choice]['other'] + ': '
749
+ response = gets
750
+
751
+ # normalize response
752
+ response.sub!(/\s+\z/imu, '')
753
+ response.sub!(/\A\s+/imu, '')
754
+
755
+ # if response has content, we're done with this loop
756
+ if response.length > 0
757
+ rv['other'] = response
758
+ end
759
+ end
760
+ end
761
+
762
+ # return
763
+ return rv
764
+ end
765
+ #
766
+ # select
767
+ #---------------------------------------------------------------------------
768
+
769
+
770
+ #---------------------------------------------------------------------------
771
+ # short
772
+ #
773
+ def self.short(opts)
774
+ # $tm.hrm
775
+
776
+ # initialize return hash
777
+ rv = {}
778
+
779
+ # loop until we get an answer
780
+ while not rv['response']
781
+ # get other choice
782
+ print opts['prompt'] + ': '
783
+ response = gets
784
+
785
+ # normalize response
786
+ response.sub!(/\s+\z/imu, '')
787
+ response.sub!(/\A\s+/imu, '')
788
+
789
+ # if response has content, or response is not required, we're done
790
+ if (response.length > 0) or (not opts['required'])
791
+ rv['response'] = response
792
+ end
793
+ end
794
+
795
+ # return
796
+ return rv
797
+ end
798
+ #
799
+ # short
800
+ #---------------------------------------------------------------------------
801
+
802
+
803
+ #---------------------------------------------------------------------------
804
+ # boolean
805
+ #
806
+ def self.boolean(opts)
807
+ # $tm.hrm
808
+
809
+ # initialize return hash
810
+ rv = {}
811
+
812
+ # output prompt
813
+ puts opts['prompt']
814
+
815
+ # boolean options
816
+ bools = {
817
+ Minvee::Client::Message.new(%w{questions yes letter}).expand => true,
818
+ Minvee::Client::Message.new(%w{questions no letter}).expand => false,
819
+ }
820
+
821
+ # initialize choices
822
+ choices = []
823
+
824
+ # yes
825
+ choices.push 'y'
826
+ choices.push 'n'
827
+
828
+ # default
829
+ if opts.has_key?('default')
830
+ has_default = true
831
+
832
+ if opts['default']
833
+ choices[0].upcase!
834
+ else
835
+ choices[1].upcase!
836
+ end
837
+ end
838
+
839
+ # ask until we get an answer
840
+ while rv['response'].nil?
841
+ # prompt
842
+ print choices.join('/'), ': '
843
+
844
+ # get response
845
+ response = gets
846
+
847
+ # normalize response
848
+ response.sub!(/\s+\z/imu, '')
849
+ response.sub!(/\A\s+/imu, '')
850
+ response.downcase!()
851
+
852
+ # if anything left in the string, get the first character
853
+ if response.length > 0
854
+ response = response[0]
855
+ end
856
+
857
+ # if we got an acceptable answer
858
+ if bools.has_key?(response)
859
+ rv['response'] = bools[response]
860
+ elsif has_default and (response == '')
861
+ rv['response'] = opts['default']
862
+ end
863
+ end
864
+
865
+ # return
866
+ return rv
867
+ end
868
+ #
869
+ # boolean
870
+ #---------------------------------------------------------------------------
871
+ end
872
+ #
873
+ # Minvee::Client::Questions
874
+ #===============================================================================
875
+
876
+
877
+ #===============================================================================
878
+ # run if the script was not loaded by another script
879
+ #
880
+ if caller().length <= 0
881
+ Minvee::Client.process_command_line ARGV
882
+ end
883
+ #
884
+ # run if the script was not loaded by another script
885
+ #===============================================================================