blavoshost 0.0.0

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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jerrod Blavos
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = blavoshost
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Jerrod Blavos. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ load File.expand_path('../bin/blavoshost', __FILE__)
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "blavoshost"
9
+ gem.summary = %Q{Add local vhosts}
10
+ gem.description = %Q{A tool to add local vhosts ot apache/OSX}
11
+ gem.email = "jerrod@indierockmedia.com"
12
+ gem.homepage = "http://github.com/jerrod/blavoshost"
13
+ gem.authors = ["Jerrod Blavos"]
14
+ gem.executables = ['blavoshost']
15
+ gem.require_paths = ["bin"] # annoying requirement
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/bin/blavoshost ADDED
@@ -0,0 +1,818 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__)) unless
3
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
4
+
5
+ require 'rubygems'
6
+ require 'json'
7
+ require 'open3'
8
+ require 'ostruct'
9
+ require 'optparse'
10
+ require 'fileutils'
11
+ require 'ruby-debug'
12
+
13
+ module Hipe
14
+ module Vhost
15
+ HardCoded = OpenStruct.new(
16
+ :execs => ['apachectl','apache2ctl'],
17
+ :httpd_conf => ['/etc/apache2/apache2.conf',
18
+ '/etc/apache2/httpd.conf'],
19
+ :docroots => '~/Sites',
20
+ :vhost_confs => '/etc/apache2/sites-enabled/',
21
+ :etc_hosts => '/etc/hosts'
22
+ )
23
+ end
24
+ end
25
+
26
+
27
+ module Hipe
28
+
29
+ # Every exception thrown directly by this library will be a kind of
30
+ # Hipe::Fail. Subclasses are things like UserFail, EnvFail and
31
+ # PermissionFail, which are exceptions resulting from something
32
+ # the user did wrong, something in the environment that won't permit us
33
+ # to carry out a request, or a permissions issue with the filesystem
34
+ # or process space; respectively. The implementing client application will
35
+ # usually want to catch these and recover gracefully from them.
36
+
37
+ # There are corresponding modules for each of these exception classes
38
+ # (a UserFail is a kind of UserFailey (the adjectivial form) for example),
39
+ # so we can enhance exceptions thrown from other libraries as well,
40
+ # and re-throw them as that original exception object with its stack intact.
41
+ # All exception classes in this library end in 'Fail', and their
42
+ # corresponding modules end in 'Failey' (the adjective form.)
43
+ # So note that an exception caught from another library and 'enhanced'
44
+ # by this one will not be a kind of Hipe::Fail, only a kind of Hipe::Failey.
45
+
46
+ # So to have your bases covered, rescue Hipe::Failey or the subset of
47
+ # Hipe::Failies that you care about rescuing.
48
+
49
+ module Failey; end
50
+ module EnvFailey; include Failey end
51
+ module UserFailey; include Failey end
52
+ module PermissionFailey; include UserFailey end
53
+
54
+ class Fail < RuntimeError; include Failey end
55
+ class UserFail < Fail; include UserFailey end
56
+ class EnvFail < Fail; include EnvFailey end
57
+ class PermissionFail < UserFail; include PermissionFailey end
58
+
59
+ # AsciiFormattey last seen in f2fa2
60
+
61
+ # this guy was moved into ack-lite-parse and modified. if you
62
+ # want any more features, start with that one and note it there
63
+ module CliLitey
64
+ # if this thing exceeds 30 lines of code use hipe-core/interfacey
65
+ def cli_run(argv)
66
+ return help_text unless argv[0] && @actions[argv[0]]
67
+ action = argv.shift
68
+ action = help if (['-h','--help'].include?(action))
69
+ meth = action
70
+ send(meth, argv)
71
+ end
72
+
73
+ def program_name
74
+ File.basename $PROGRAM_NAME
75
+ end
76
+
77
+ def help(args)
78
+ help_text+"\n\n"
79
+ end
80
+ end
81
+
82
+ module FileyCoyote
83
+ def self.writable!(path)
84
+ if ! File.writable?(path)
85
+ raise PermissionFail.new("Can't write to #{path}")
86
+ end
87
+ @path = path
88
+ nil
89
+ end
90
+ def writable!
91
+ FileyCoyote.writable!(@path)
92
+ self
93
+ end
94
+ def fileize str
95
+ FileyCoyote.fileize str
96
+ end
97
+ def self.fileize str
98
+ str.gsub(/[^-\.a-zA-Z0-9]/,'')
99
+ end
100
+ def back_that_asset_up! orig_path
101
+ dry = respond_to?(:dry_run?) ? dry_run? : false
102
+ append = Time.now.strftime('%Y-%m-%d_%H:%M:%S')
103
+ new_name = %{#{orig_path}.bak.#{append}}
104
+ i = 1
105
+ while File.exist? new_name # very unlikely
106
+ new_name = %{#{orig_path}.bak.#{append}.#{i}}
107
+ i += 1
108
+ end
109
+ begin
110
+ FileUtils.copy_file orig_path, new_name unless dry
111
+ rescue Errno::EACCES => e
112
+ e.extend OpenStructey
113
+ e.extend PermissionFailey
114
+ e.filename = new_name
115
+ raise e
116
+ end
117
+ new_name
118
+ end
119
+ end
120
+
121
+ module DryRunney
122
+ def dry_run= val
123
+ @dry_run = val
124
+ end
125
+ def dry_run?
126
+ @dry_run
127
+ end
128
+ alias_method :noop, :dry_run?
129
+ def dry_run!
130
+ @dry_run = true
131
+ end
132
+ def report
133
+ wtf = @report ||= []
134
+ wtf
135
+ end
136
+ end
137
+
138
+ module OpenStructey
139
+ def self.extend_object(base)
140
+ base.instance_eval do
141
+ @__info = {}
142
+ end
143
+ super
144
+ end
145
+ def method_missing name, *args, &blah
146
+ if /^(.+)=$/ =~ name.to_s
147
+ @__info[$1.to_sym] = args[0]
148
+ else
149
+ @__info[name.to_sym]
150
+ end
151
+ end
152
+ end
153
+
154
+ module Systematic
155
+ def sistum command
156
+ stdin, stdout, stderr = Open3.popen3 command
157
+ err = stderr.read; out = stdout.read
158
+ raise Fail.new(err) if "" != err
159
+ out.chop
160
+ end
161
+
162
+ def sistum2 command
163
+ stdin, stdout, stderr = Open3.popen3 command
164
+ err = stderr.read; out = stdout.read
165
+ [out.chop,err.chop]
166
+ end
167
+ end
168
+
169
+ module Templatesimal
170
+ # the last thing the world needs is...
171
+
172
+ NamePattern = /\{\{([^\}]+)\}\}/ # e.g. "{{title}}"
173
+
174
+ def self.[] str
175
+ str.extend self
176
+ str.init_templatesimal
177
+ str
178
+ end
179
+
180
+ def init_templatesimal
181
+ names = scan(NamePattern).map{|x| x[0]}.uniq
182
+ meta = class << self; end
183
+ names.each do |name|
184
+ unless respond_to?(name)
185
+ meta = class << self; self; end
186
+ meta.send(:define_method, :"#{name}=") do |x|
187
+ template_replace(name, x)
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ def template_replace name, value
194
+ gsub! "{{#{name}}}", value
195
+ end
196
+
197
+ def substitute! hash
198
+ hash.each{|pair| template_replace(pair[0],pair[1]) }
199
+ self
200
+ end
201
+ end
202
+
203
+ class AsciiTableLite
204
+ #
205
+ # For now this is the official home of this here in vhost.
206
+ # This is a simplified version of hipe-core/struct/table
207
+ # Note any other projects that copy paste this here:
208
+ # [none]
209
+ #
210
+
211
+ def initialize(*cols)
212
+ @cols = cols
213
+ @pipe = '|'
214
+ @cona = '+'
215
+ @rule = '-'
216
+ @rulesep = @rule * 3
217
+ @sep = " #{@pipe} "
218
+ end
219
+
220
+ def render rows
221
+ @rows = rows
222
+ @maxes = Hash[*@cols.map{|x|[x, x.to_s.length]}.flatten]
223
+ prerendereds = []
224
+ rows.each do |info|
225
+ preprender = OpenStruct.new
226
+ @cols.each do |col|
227
+ cel = info.send(col).to_s
228
+ preprender.send("#{col}=",cel)
229
+ if (@maxes[col].nil? || cel.length > @maxes[col])
230
+ @maxes[col] = cel.length
231
+ end
232
+ end
233
+ prerendereds.push preprender
234
+ end
235
+ @lines = [go_sep]
236
+ @lines.push go_headers
237
+ @lines.push go_sep
238
+ prerendereds.each do |info|
239
+ @lines.push go_row info
240
+ end
241
+ @lines.push go_sep
242
+ @lines * "\n"
243
+ end
244
+
245
+ ## private
246
+
247
+ def go_sep
248
+ "#{@cona}#{@rule}"<<
249
+ "#{@cols.map{|x| @rule * @maxes[x]} * @rulesep }"<<
250
+ "#{@rule}#{@cona}"
251
+ end
252
+
253
+ def go_headers
254
+ "#{@pipe} "<<
255
+ (@cols.map{|x|x.to_s.ljust(@maxes[x])}* @sep)<<
256
+ " #{@pipe}"
257
+ end
258
+
259
+ def go_row row
260
+ "#{@pipe} "<<
261
+ (@cols.map{|x|row.send(x).to_s.ljust(@maxes[x])}*@sep)<<
262
+ " #{@pipe}"
263
+ end
264
+ end
265
+
266
+ class UnitOfWork
267
+ include DryRunney
268
+ attr_reader :describe
269
+ def initialize(desc, &block)
270
+ @describe = desc
271
+ @block = block
272
+ end
273
+ def empty?
274
+ @block.nil?
275
+ end
276
+ def commit
277
+ @block.call(self) if @block
278
+ end
279
+ end
280
+
281
+ module UnitsOfWork
282
+ def add_unit_of_work(*args, &block)
283
+ units_of_work.push UnitOfWork.new(*args,&block)
284
+ end
285
+ def units_of_work
286
+ @units_of_work ||= []
287
+ end
288
+ def run_units_of_work dry_run=false
289
+ num_done = 0
290
+ units_of_work.each do |unit|
291
+ if unit.empty?
292
+ puts unit.describe
293
+ else
294
+ unit.dry_run = dry_run
295
+ unit.commit
296
+ puts unit.describe
297
+ unit.report.each do |line|
298
+ puts " #{line}"
299
+ end
300
+ num_done += 1
301
+ end
302
+ end
303
+ num_done
304
+ end
305
+ end
306
+
307
+ module Vhost
308
+
309
+ class Fail < RuntimeError; end
310
+
311
+ class Cli
312
+ include Hipe::CliLitey, Hipe::Systematic, Hipe::UnitsOfWork
313
+ Table = Hipe::AsciiTableLite
314
+
315
+ def initialize
316
+ @actions = {'help'=>1,'explain'=>1,'hosts'=>1,'confs'=>1,
317
+ 'dump' =>1, 'list'=>1, 'add'=>1
318
+ }
319
+ @path = {}
320
+ %w(docroots vhost_confs etc_hosts).each do |guy|
321
+ @path[guy.to_sym] = File.expand_path HardCoded.send(guy)
322
+ end
323
+ these = HardCoded.httpd_conf
324
+ one = these.detect{ |x| File.exist?(x) }
325
+ one or fail("not found: #{these.join(', ')}")
326
+ @path[:httpd_conf] = one
327
+ get_orientated_with_your_environment
328
+ @path = OpenStruct.new(@path)
329
+ end
330
+
331
+ def run argv
332
+ begin
333
+ cli_run argv
334
+ rescue PermissionFailey => e
335
+ return "#{e.message} - do you need to run this with sudo?"
336
+ rescue EnvFailey, UserFailey => e
337
+ return "#{e.message}"
338
+ end
339
+ end
340
+
341
+ def explain args; HardCoded.explain end
342
+
343
+ def help_text
344
+ HardCoded.help.clone.substitute!(:program_name => program_name)
345
+ end
346
+
347
+ ###### command implementations
348
+
349
+ #def explain argv; HardCoded.explain end
350
+
351
+ def hosts argv
352
+ rows = parse_hosts.sort!{|x,y| x.name <=> y.name }
353
+ Table.new(:ip, :name).render(rows)
354
+ end
355
+
356
+ def confs argv
357
+ rows = parse_confs.sort!{|x,y| x.server_name <=> y.server_name }
358
+ Table.new(:server_name,:document_root).render(rows)
359
+ end
360
+
361
+ def dump argv
362
+ parse = parse_dump_vhosts
363
+ puts Table.new(:server_name, :port, :conf_path).
364
+ render(parse.entries)
365
+ puts "\n"
366
+ puts Table.new(:missing_docroot).
367
+ render(parse.missing_docroots)
368
+ ''
369
+ end
370
+
371
+ def list argv
372
+ infos = master.values.sort!{|x,y| x.host <=> y.host}
373
+ Table.new(
374
+ :host, :host_ok, :ip, :port, :docroot, :docroot_ok, :conf
375
+ ).render(infos)
376
+ end
377
+
378
+ def add argv
379
+ opts = OpenStruct.new(
380
+ :dry_run => false,
381
+ :docroots => @path.docroots
382
+ )
383
+ parser = OptionParser.new do |o|
384
+ o.on('-n','--dry-run'){ opts.dry_run = true; }
385
+ o.on('-d','--docroots PATH') do |v|
386
+ @path.docroots = File.expand_path(v)
387
+ end
388
+ o.on('-h','--help') do
389
+ return "Usage: #{program_name} #{Cli.add_syntax}"
390
+ end
391
+ end
392
+ begin
393
+ parser.parse! argv
394
+ rescue OptionParser::ParseError => e
395
+ puts e.message
396
+ puts "Usage: #{program_name} #{Cli.add_syntax}"
397
+ return ''
398
+ end
399
+ errs = [];
400
+ if (argv.length == 0)
401
+ errs << "too few argments. expecting one."
402
+ elsif (argv.length > 1)
403
+ errs << "too many arguments. expecting one."
404
+ elsif (! (argv[0].to_s =~ EtcHostsProxy::OkNameRe))
405
+ errs << "invalid name for a domain(?): #{argv[0].inspect}"
406
+ end
407
+ if errs.length > 0
408
+ return ((errs * "\n") << "\nUsage: #{Cli.add_syntax}")
409
+ end
410
+ new_hostname = argv[0]
411
+ _add new_hostname, opts
412
+ end
413
+
414
+ ###### top secret implementationz
415
+
416
+ def self.add_syntax
417
+ "add [-n|--dry-run] [-d|--docroots <path>] <host>"
418
+ end
419
+
420
+ def _add host, opts
421
+ item = master[host] # might be empty object
422
+ units_of_work.clear
423
+ go_etc_hosts item, host
424
+ go_config item, host
425
+ go_html item, host
426
+ num = run_units_of_work opts.dry_run
427
+ if (num==0)
428
+ puts "nothing to be done for #{host}."
429
+ else
430
+ puts "if this wasn't a dry run, you could restart apache with:"
431
+ puts " #{@path.apachectl} graceful"
432
+ puts "and go to:"
433
+ puts " http://#{host}/index.html"
434
+ end
435
+ 'done.'
436
+ end
437
+
438
+ def go_etc_hosts item, host
439
+ if 'ok'==item.host_ok
440
+ add_unit_of_work("#{@path.etc_hosts} ok for #{host}")
441
+ else
442
+ hosts = EtcHostsProxy.new(@path.etc_hosts).writable!
443
+ add_unit_of_work("add line to #{@path.etc_hosts}:") do |unit|
444
+ hosts.dry_run = unit.dry_run?
445
+ hosts.add! host
446
+ unit.report.concat hosts.report
447
+ end
448
+ end
449
+ end
450
+
451
+ def go_config item, host
452
+ if item.conf
453
+ add_unit_of_work("config ok for #{host}")
454
+ else
455
+ doc_root = File.join(@path.docroots, FileyCoyote.fileize(host))
456
+ confs = VhostFileMakerPro.new(apache_conf_proxy.sites_enabled_path)
457
+ add_unit_of_work("add config file for #{host}:") do |unit|
458
+ confs.dry_run = unit.dry_run?
459
+ confs.add! host, doc_root
460
+ unit.report.concat confs.report
461
+ end
462
+ end
463
+ end
464
+
465
+ def go_html item, host
466
+ if 'ok'==item.docroot_ok
467
+ add_unit_of_work("#{@path.docroots} ok for #{host}")
468
+ else
469
+ docroot = DocRootProxy.new(@path.docroots).writable!
470
+ add_unit_of_work("add stub website to #{@path.docroots}:")do |unit|
471
+ docroot.dry_run = unit.dry_run?
472
+ docroot.make_stub_site(host)
473
+ unit.report.concat docroot.report
474
+ end
475
+ end
476
+ end
477
+
478
+ def parse_hosts
479
+ EtcHostsProxy.new(@path.etc_hosts).entries
480
+ end
481
+
482
+ def parse_confs
483
+ conf = apache_conf_proxy
484
+ confs = conf.sites_enabled_confs
485
+ confs.delete_if{|x| x.document_root == '' }
486
+ confs.sort!{|x,y| x.server_name <=> y.server_name}
487
+ end
488
+
489
+ def apache_conf_proxy
490
+ @apache_conf_proxy ||= ApacheConfProxy.new @path.httpd_conf
491
+ end
492
+
493
+ def parse_dump_vhosts
494
+ cmd = "#{@path.apachectl} -t -D DUMP_VHOSTS"
495
+ out,err = sistum2(cmd)
496
+ lines = out.split("\n");
497
+ lines.concat err.split("\n");
498
+ # on ubuntu, we get 'Syntax OK' in out, and details in err
499
+ # on osx, we get empty out and everything in err
500
+ p = DumpParse.new lines.join("\n") # @todo plan on having this crap out a lot
501
+ p
502
+ end
503
+
504
+ def get_orientated_with_your_environment
505
+ list = HardCoded.execs
506
+ infos = list.map do |executable|
507
+ OpenStruct.new(
508
+ 'which' => sistum("which #{executable}")
509
+ )
510
+ end
511
+ infos.delete_if{|x| ""==x.which}
512
+ case infos.length
513
+ when 0:
514
+ raise Fail.new("#{sistum('whoami')} has neither "<<
515
+ (list * ' nor ') << "within his or her executable path."<<
516
+ " I can be of no help to you."
517
+ )
518
+ when 1: # fallthru
519
+ else
520
+ raise Fail.new("#{sistum('whoami')} has all of these in"<<
521
+ " his or her path: "<< (infos.map(&:which) * ' and ')<<'.'<<
522
+ " As this is a zero-configuration jobbie, I have no idea"<<
523
+ " what to do."
524
+ )
525
+ end
526
+ @path[:apachectl] = infos[0].which
527
+ end
528
+
529
+ def master
530
+ hosts = parse_hosts
531
+ confs = parse_confs
532
+ dumps = parse_dump_vhosts # entries, missing_doctroots
533
+ missings = Hash[*
534
+ dumps.missing_docroots.each.map{|x| [x.missing_docroot, 1]}.flatten
535
+ ]
536
+ master = Hash.new(){|h,k| h[k] = OpenStruct.new(:host=>k) }
537
+ hosts.each do |host|
538
+ item = master[host.name]
539
+ item.ip = host.ip
540
+ item.host_ok = 'ok'
541
+ end
542
+ confs.each do |conf|
543
+ item = master[conf.server_name]
544
+ item.docroot = conf.document_root
545
+ item.docroot_ok = (missings[conf.document_root]) ? 'missing' : 'ok'
546
+ end
547
+ dumps.entries.each do |dump|
548
+ item = master[dump.server_name]
549
+ item.conf = dump.conf_path
550
+ item.port = dump.port
551
+ end
552
+ master
553
+ end
554
+ end # Cli
555
+
556
+ class DumpParse
557
+ attr_reader :missing_docroots, :entries
558
+ def initialize str
559
+ lines = str.split("\n")
560
+ @missing_docroots = []
561
+ infos = []
562
+ lines.each do |line|
563
+ case line
564
+ when /^Warning: DocumentRoot \[(.+)\] does not exist$/:
565
+ @missing_docroots.push OpenStruct.new(:missing_docroot => $1)
566
+ when /^.*is a NameVirtualHost$/: #skip
567
+ when /^wildcard NameVirtualHosts and _default_ servers: *$/: #skip
568
+ when "VirtualHost configuration:": # skip
569
+ when /^ {9}default server (.+) \((.+)\:\d+\)$/:
570
+ infos.push OpenStruct.new(
571
+ :server_name => $1, :conf_path => $2, :port => nil
572
+ )
573
+ when /^ {9}port ([^ ]+) namevhost (.+) \((.+)\:\d+\)$/:
574
+ infos.push OpenStruct.new(
575
+ :server_name => $2, :conf_path => $3, :port => $1
576
+ )
577
+ when /^Syntax OK$/: #skip for now end
578
+ when /^httpd: Could not reliably determine/: #skip
579
+ when /^\*:(\d{4}) +([^ ]+) +\((.+)\:\d+\)$/
580
+ infos.push OpenStruct.new(
581
+ :server_name => $2, :conf_path => $3, :port => $1
582
+ )
583
+ else
584
+ raise Fail.new("we suck: parse fail:\n#{line.inspect}")
585
+ end
586
+ end
587
+ @entries = infos
588
+ end
589
+ end
590
+
591
+ class ApacheConfProxy
592
+ include DryRunney
593
+ SitesEnabledRe = %r|^ *Include +([^\n]*sites-enabled/?) *$|m
594
+ # code for pasring the file itself left behind in bf91c
595
+ def initialize path
596
+ @path = path
597
+ if !File.readable?(@path)
598
+ raise UserFail.new("no such apache conf file or not readable: #{@path}")
599
+ end
600
+ @contents = File.read path
601
+ end
602
+
603
+ # this project it is hard coded to expect that there is
604
+ # only one such entry in the file
605
+ def sites_enabled_path
606
+ arr = @contents.scan(SitesEnabledRe).map{|x| x[0]}
607
+ case arr.length
608
+ when 0:
609
+ `sudo bash -c 'sudo echo "Include /private/etc/apache2/sites-enabled/*.conf" >> /etc/apache2/httpd.conf'`
610
+ `sudo bash -c 'sudo mkdir /private/etc/apache2/sites-enabled '`
611
+ return '/private/etc/apache2/sites-enabled/'
612
+ when 1: arr[0]
613
+ else arr
614
+ end
615
+ end
616
+
617
+ def sites_enabled_confs
618
+ path1 = sites_enabled_path
619
+ paths = Dir[File.join(path1,'*.conf')]
620
+ paths.map do |path|
621
+ VhostConfProxy.new(path)
622
+ end
623
+ end
624
+ end
625
+
626
+ class VhostFileMakerPro
627
+ include DryRunney
628
+ def initialize path
629
+ unless File.directory? path
630
+ raise EnvFail.new "it's not exist: #{path}"
631
+ end
632
+ unless File.writable? path
633
+ raise PermissionFail.new "not writable: #{path}"
634
+ end
635
+ @path = path
636
+ end
637
+ def add! host, doc_root
638
+ path = File.join(@path, "#{host}.conf")
639
+ if File.exist? path
640
+ report << "File already existed: #{path}"
641
+ else
642
+ contents = HardCoded.vhost_stuff.clone
643
+ contents.doc_root = doc_root
644
+ contents.host = host
645
+ unless dry_run?
646
+ File.open(path,'a+'){|fh|
647
+ fh.write contents
648
+ }
649
+ end
650
+ report << "write #{path}"
651
+ end
652
+ nil
653
+ end
654
+ end
655
+
656
+ class VhostConfProxy < ApacheConfProxy
657
+ def initialize *args
658
+ super(*args)
659
+ @cache = {}
660
+ end
661
+ def document_root
662
+ _get('DocumentRoot')
663
+ end
664
+ def server_name
665
+ _get('ServerName')
666
+ end
667
+ def _get thing
668
+ @cache[thing] ||= begin
669
+ re = Regexp.new('^ *'<<Regexp.escape(thing)<<
670
+ ' *"?([^"]+)"? *$')
671
+ matches = @contents.scan(re)
672
+ case matches.length
673
+ when 0: ""
674
+ when 1: matches[0][0]
675
+ else matches.map{|x| x[0]}.join(',')
676
+ end
677
+ end
678
+ end
679
+ end
680
+
681
+ class DocRootProxy
682
+ include FileyCoyote, DryRunney
683
+ def initialize path
684
+ @path = path
685
+ end
686
+ def writable!
687
+ unless File.directory? @path
688
+ raise EnvFail.new(
689
+ "docroots directory (the directory to hold your docroot "<<
690
+ "directory) must exist and be directory: #{@path}")
691
+ end
692
+ FileyCoyote.writable! @path
693
+ self
694
+ end
695
+ def make_stub_site host
696
+ @host = host
697
+ if (doc_root_exists?)
698
+ raise Fail.new("already exists: #{doc_root}")
699
+ end
700
+ report << "mkdir -m 755 #{doc_root}"
701
+ FileUtils.mkdir(doc_root,:mode=>0755, :verbose=>false,:noop=>dry_run?)
702
+ index_html = File.join(doc_root,"index.html")
703
+ my_new_homepage = HardCoded.web_page.clone
704
+ my_new_homepage.title = host.gsub(/_|-/,' ')
705
+ unless dry_run?
706
+ File.open(index_html,'a+'){|fh| fh.write my_new_homepage }
707
+ end
708
+ report << "write #{index_html}"
709
+ nil
710
+ end
711
+ protected
712
+ def doc_root
713
+ doc_root = File.join(@path, fileize(@host))
714
+ end
715
+ def doc_root_exists?
716
+ File.exist?(doc_root)
717
+ end
718
+ end
719
+
720
+ class EtcHostsProxy
721
+ include DryRunney
722
+ OkNameRe = /^[-a-z_0-9\.]{1,}$/
723
+ include FileyCoyote, DryRunney
724
+ def initialize path
725
+ raise Fail.new("no: #{path}") unless File.exist?(path)
726
+ @path = path
727
+ end
728
+ def entries
729
+ File.read(@path).scan(
730
+ %r{(127\.0\.0\.1)\s+([[:print:]]+)[\s\t]*$}
731
+ ).map do |match|
732
+ OpenStruct.new(:ip => match[0], :name => match[1])
733
+ end
734
+ end
735
+ def add! name
736
+ writable!
737
+ raise UserFail.new("i don't like your name: #{name.inspect}") unless
738
+ (name =~ OkNameRe)
739
+ info = OpenStruct.new
740
+ info.backed_up = back_that_asset_up! @path
741
+ report.push "make backup: #{info.backed_up}"
742
+ File.open(@path,'a+') do |fh|
743
+ fh.seek(0,IO::SEEK_END)
744
+ fh.puts %{127.0.0.1 #{name}}
745
+ info.hostname = name
746
+ info.ip = '127.0.0.1'
747
+ end unless dry_run?
748
+ report.push %{add "#{name}" to #{@path}}
749
+ info
750
+ end
751
+ end
752
+
753
+
754
+ HardCoded.help = Templatesimal[<<-END.gsub(/^ {6}/,'')
755
+ do some stuff with your apache server virtualhosts.
756
+
757
+ Usage: {{program_name}} COMMAND [ARGUMENTS]
758
+
759
+ Commands:
760
+ help show this screen
761
+ explain explains the steps involved in adding a virtualhost
762
+ -- you should understand this before using this script
763
+ hosts just shows the parsed version of the /etc/hosts file
764
+ confs just list the known vhosts per the sites-enabled dir.
765
+ dump a tableized version of `apachectl -t -D DUMP_VHOSTS`
766
+ list report everything you know about anything
767
+ #{Cli.add_syntax}
768
+ add stuff to all 3 places if necessary
769
+ END
770
+ ]
771
+
772
+ HardCoded.explain = Templatesimal[<<-END.gsub(/^ {6}/,'')
773
+ HOW TO ADD A VIRTUALHOST (the really short version):
774
+ 1) `locate httpd.conf` # then jump to the relevant lines at the end
775
+ 2) copy paste a new virtualhost section into the file -or-
776
+ add such vhost config file into a folder that gets Include'd
777
+ 3) `locate /etc/hosts` # find the right file, it's usually this one
778
+ 4) copy paste your line
779
+ 5) apache(2?)ctl graceful
780
+ END
781
+ ]
782
+
783
+ HardCoded.web_page = Templatesimal[<<-END.gsub(/^ {6}/,'')
784
+ <!DOCTYPE html><html><head><title>{{title}}</title>
785
+ <style type="text/css">
786
+ body {font-family: sans-serif; background-color: #6cc5c3; }
787
+ h1,h2,h3,h4,h5 {
788
+ text-align: center; background-color: #f05921; color: #ffffff;
789
+ -webkit-border-radius: 8px; /* for Safari */
790
+ -moz-border-radius: 8px; /* for Firefox */
791
+ padding: .23em;
792
+ text-shadow:0 0 10px #000000;
793
+ }
794
+ </style>
795
+ </head><body>
796
+ <h1>welcome to {{title}}</h1>
797
+ </body></html>
798
+ END
799
+ ]
800
+
801
+ HardCoded.vhost_stuff = Templatesimal[<<-END.gsub(/^ {6}/,'')
802
+ <VirtualHost *:80>
803
+ DocumentRoot "{{doc_root}}"
804
+ ServerName "{{host}}"
805
+ <Directory "{{doc_root}}">
806
+ AllowOverride All
807
+ Allow from All
808
+ </Directory>
809
+ </VirtualHost>
810
+ END
811
+ ]
812
+
813
+ end # Vhost
814
+ end # Hipe
815
+
816
+ if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
817
+ puts Hipe::Vhost::Cli.new.run(ARGV)
818
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'blavoshost'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestBlavoshost < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blavoshost
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Jerrod Blavos
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-25 00:00:00 -04:00
19
+ default_executable: blavoshost
20
+ dependencies: []
21
+
22
+ description: A tool to add local vhosts ot apache/OSX
23
+ email: jerrod@indierockmedia.com
24
+ executables:
25
+ - blavoshost
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - LICENSE
30
+ - README.rdoc
31
+ files:
32
+ - .document
33
+ - .gitignore
34
+ - LICENSE
35
+ - README.rdoc
36
+ - Rakefile
37
+ - VERSION
38
+ - bin/blavoshost
39
+ - test/helper.rb
40
+ - test/test_blavoshost.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/jerrod/blavoshost
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - bin
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.7
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Add local vhosts
75
+ test_files:
76
+ - test/helper.rb
77
+ - test/test_blavoshost.rb