Agiley-ec2onrails 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- data/{History.txt → CHANGELOG} +0 -0
- data/{COPYING.txt → COPYING} +0 -0
- data/Manifest +161 -0
- data/{website/index.txt → README.textile} +33 -5
- data/Rakefile +36 -4
- data/TODO +91 -0
- data/ec2onrails.gemspec +279 -0
- data/examples/Capfile +3 -0
- data/examples/deploy.rb +88 -0
- data/examples/s3.yml +9 -0
- data/lib/ec2onrails/capistrano_utils.rb +0 -11
- data/lib/ec2onrails/recipes.rb +165 -59
- data/lib/ec2onrails/version.rb +1 -1
- data/server/build-ec2onrails.sh +44 -0
- data/server/files/etc/aliases +5 -0
- data/server/files/etc/aliases.db +0 -0
- data/server/files/etc/apache2/apache2.conf +295 -0
- data/server/files/etc/apache2/conf.d/app.proxy_cluster.conf +7 -0
- data/server/files/etc/apache2/conf.d/app.proxy_frontend.conf +10 -0
- data/server/files/etc/apache2/mods-available/proxy.conf +18 -0
- data/server/files/etc/apache2/sites-available/app.common +56 -0
- data/server/files/etc/apache2/sites-available/app.custom +0 -0
- data/server/files/etc/apache2/sites-available/default +14 -0
- data/server/files/etc/apache2/sites-available/default-ssl +18 -0
- data/server/files/etc/cron.d/backup_app_db_to_s3 +6 -0
- data/server/files/etc/cron.daily/app +9 -0
- data/server/files/etc/cron.daily/logrotate_post +19 -0
- data/server/files/etc/cron.hourly/app +10 -0
- data/server/files/etc/cron.monthly/app +10 -0
- data/server/files/etc/cron.weekly/app +10 -0
- data/server/files/etc/ec2onrails/balancer_members +6 -0
- data/server/files/etc/ec2onrails/roles.yml +5 -0
- data/server/files/etc/environment +2 -0
- data/server/files/etc/event.d/monit +13 -0
- data/server/files/etc/init.d/ec2-every-startup +29 -0
- data/server/files/etc/init.d/ec2-first-startup +36 -0
- data/server/files/etc/init.d/mongrel +91 -0
- data/server/files/etc/init.d/nginx +78 -0
- data/server/files/etc/init.d/set_roles +3 -0
- data/server/files/etc/logrotate.d/apache2 +16 -0
- data/server/files/etc/logrotate.d/mongrel +11 -0
- data/server/files/etc/logrotate.d/nginx +11 -0
- data/server/files/etc/memcached.conf +47 -0
- data/server/files/etc/mongrel_cluster/app.yml +9 -0
- data/server/files/etc/monit/README +5 -0
- data/server/files/etc/monit/app.monitrc.erb +13 -0
- data/server/files/etc/monit/db_primary.monitrc.erb +10 -0
- data/server/files/etc/monit/memcache.monitrc +8 -0
- data/server/files/etc/monit/monitrc +12 -0
- data/server/files/etc/monit/system.monitrc +15 -0
- data/server/files/etc/monit/web.monitrc.erb +23 -0
- data/server/files/etc/motd.tail +13 -0
- data/server/files/etc/mysql/my.cnf +149 -0
- data/server/files/etc/nginx/nginx.conf +296 -0
- data/server/files/etc/postfix/main.cf +4 -0
- data/server/files/etc/rc0.d/K10mongrel +1 -0
- data/server/files/etc/rc1.d/K10mongrel +1 -0
- data/server/files/etc/rc2.d/S90mongrel +1 -0
- data/server/files/etc/rc3.d/S90mongrel +1 -0
- data/server/files/etc/rc4.d/S90mongrel +1 -0
- data/server/files/etc/rc5.d/S90mongrel +1 -0
- data/server/files/etc/rc6.d/K10mongrel +1 -0
- data/server/files/etc/rcS.d/S91ec2-first-startup +1 -0
- data/server/files/etc/rcS.d/S92ec2-every-startup +1 -0
- data/server/files/etc/rcS.d/S99set_roles +1 -0
- data/server/files/etc/ssh/sshd_config +94 -0
- data/server/files/etc/sudoers +1 -0
- data/server/files/etc/sudoers.full_access +26 -0
- data/server/files/etc/sudoers.restricted_access +28 -0
- data/server/files/etc/syslog.conf +69 -0
- data/server/files/usr/local/ec2onrails/COPYING +339 -0
- data/server/files/usr/local/ec2onrails/bin/archive_file.rb +44 -0
- data/server/files/usr/local/ec2onrails/bin/backup_app_db.rb +68 -0
- data/server/files/usr/local/ec2onrails/bin/init_services.rb +57 -0
- data/server/files/usr/local/ec2onrails/bin/mongrel_start +8 -0
- data/server/files/usr/local/ec2onrails/bin/mongrel_stop +8 -0
- data/server/files/usr/local/ec2onrails/bin/optimize_mysql.rb +339 -0
- data/server/files/usr/local/ec2onrails/bin/rails_env +35 -0
- data/server/files/usr/local/ec2onrails/bin/rebundle.sh +70 -0
- data/server/files/usr/local/ec2onrails/bin/restore_app_db.rb +58 -0
- data/server/files/usr/local/ec2onrails/bin/set_rails_env +40 -0
- data/server/files/usr/local/ec2onrails/bin/set_roles.rb +76 -0
- data/server/files/usr/local/ec2onrails/bin/setup_web_proxy.rb +106 -0
- data/server/files/usr/local/ec2onrails/config +30 -0
- data/server/files/usr/local/ec2onrails/lib/mysql_helper.rb +82 -0
- data/server/files/usr/local/ec2onrails/lib/roles_helper.rb +137 -0
- data/server/files/usr/local/ec2onrails/lib/s3_helper.rb +126 -0
- data/server/files/usr/local/ec2onrails/lib/utils.rb +16 -0
- data/server/files/usr/local/ec2onrails/lib/vendor/ini.rb +268 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/every-startup/get-hostname.sh +27 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/README +5 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/create-dirs.sh +42 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/generate-default-web-cert-and-key.sh +49 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/misc.sh +27 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/prepare-mysql-data-dir.sh +24 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/setup-credentials.sh +29 -0
- data/server/rakefile.rb +222 -0
- data/test/autobench.conf +60 -0
- data/test/spec/lib/s3_helper_spec.rb +134 -0
- data/test/spec/lib/s3_old.yml +3 -0
- data/test/spec/test_files/test1 +0 -0
- data/test/spec/test_files/test2 +0 -0
- data/test/test_app/Capfile +3 -0
- data/test/test_app/README +182 -0
- data/test/test_app/Rakefile +10 -0
- data/test/test_app/app/controllers/application.rb +7 -0
- data/test/test_app/app/controllers/db_fast_controller.rb +6 -0
- data/test/test_app/app/controllers/fast_controller.rb +5 -0
- data/test/test_app/app/controllers/slow_controller.rb +6 -0
- data/test/test_app/app/controllers/very_slow_controller.rb +6 -0
- data/test/test_app/app/helpers/application_helper.rb +3 -0
- data/test/test_app/app/helpers/db_fast_helper.rb +2 -0
- data/test/test_app/app/helpers/fast_helper.rb +2 -0
- data/test/test_app/app/helpers/slow_helper.rb +2 -0
- data/test/test_app/app/helpers/very_slow_helper.rb +2 -0
- data/test/test_app/config/boot.rb +109 -0
- data/test/test_app/config/database.yml +19 -0
- data/test/test_app/config/deploy.rb +21 -0
- data/test/test_app/config/environment.rb +60 -0
- data/test/test_app/config/environments/development.rb +21 -0
- data/test/test_app/config/environments/production.rb +18 -0
- data/test/test_app/config/environments/test.rb +19 -0
- data/test/test_app/config/routes.rb +27 -0
- data/test/test_app/db/schema.rb +7 -0
- data/test/test_app/doc/README_FOR_APP +2 -0
- data/test/test_app/public/404.html +30 -0
- data/test/test_app/public/500.html +30 -0
- data/test/test_app/public/dispatch.cgi +10 -0
- data/test/test_app/public/dispatch.fcgi +24 -0
- data/test/test_app/public/dispatch.rb +10 -0
- data/test/test_app/public/favicon.ico +0 -0
- data/test/test_app/public/images/rails.png +0 -0
- data/test/test_app/public/javascripts/application.js +2 -0
- data/test/test_app/public/javascripts/controls.js +963 -0
- data/test/test_app/public/javascripts/dragdrop.js +972 -0
- data/test/test_app/public/javascripts/effects.js +1120 -0
- data/test/test_app/public/javascripts/prototype.js +4225 -0
- data/test/test_app/public/robots.txt +1 -0
- data/test/test_app/script/about +3 -0
- data/test/test_app/script/breakpointer +3 -0
- data/test/test_app/script/console +3 -0
- data/test/test_app/script/destroy +3 -0
- data/test/test_app/script/generate +3 -0
- data/test/test_app/script/performance/benchmarker +3 -0
- data/test/test_app/script/performance/profiler +3 -0
- data/test/test_app/script/performance/request +3 -0
- data/test/test_app/script/plugin +3 -0
- data/test/test_app/script/process/inspector +3 -0
- data/test/test_app/script/process/reaper +3 -0
- data/test/test_app/script/process/spawner +3 -0
- data/test/test_app/script/runner +3 -0
- data/test/test_app/script/server +3 -0
- data/test/test_app/test/functional/db_fast_controller_test.rb +18 -0
- data/test/test_app/test/functional/fast_controller_test.rb +18 -0
- data/test/test_app/test/functional/slow_controller_test.rb +18 -0
- data/test/test_app/test/functional/very_slow_controller_test.rb +18 -0
- metadata +193 -36
- data/Manifest.txt +0 -25
- data/README.txt +0 -1
- data/config/hoe.rb +0 -70
- data/config/requirements.rb +0 -17
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/script/txt2html +0 -74
- data/tasks/deployment.rake +0 -27
- data/tasks/environment.rake +0 -7
- data/tasks/website.rake +0 -17
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -144
- data/website/template.rhtml +0 -53
@@ -0,0 +1,137 @@
|
|
1
|
+
# This file is part of EC2 on Rails.
|
2
|
+
# http://rubyforge.org/projects/ec2onrails/
|
3
|
+
#
|
4
|
+
# Copyright 2007 Paul Dowman, http://pauldowman.com/
|
5
|
+
#
|
6
|
+
# EC2 on Rails is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# EC2 on Rails is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'net/http'
|
20
|
+
require 'pp'
|
21
|
+
require 'socket'
|
22
|
+
require 'yaml'
|
23
|
+
require 'erb'
|
24
|
+
|
25
|
+
module Ec2onrails
|
26
|
+
module RolesHelper
|
27
|
+
ROLES_FILE = "/etc/ec2onrails/roles.yml"
|
28
|
+
MONGREL_CONF_FILE = "/etc/mongrel_cluster/app.yml"
|
29
|
+
|
30
|
+
def local_address
|
31
|
+
@local_address ||= get_address_metadata "local-ipv4"
|
32
|
+
end
|
33
|
+
|
34
|
+
def public_address
|
35
|
+
@public_address ||= get_address_metadata "public-ipv4"
|
36
|
+
end
|
37
|
+
|
38
|
+
def roles
|
39
|
+
@roles ||= resolve_all_addresses(YAML::load_file(ROLES_FILE))
|
40
|
+
end
|
41
|
+
|
42
|
+
def start(role, service, prog_name = service)
|
43
|
+
puts "STARTING #{role} role (service: #{service}, program_name: #{prog_name})"
|
44
|
+
# ensure script is executable
|
45
|
+
run "chmod a+x /etc/init.d/#{service}"
|
46
|
+
# start service if not running:
|
47
|
+
unless (system("pidof -x #{prog_name}"))
|
48
|
+
run "sh /etc/init.d/#{service} start && sleep 30" # give the service 30 seconds to start before attempting to monitor it
|
49
|
+
end
|
50
|
+
run "monit -g #{role} monitor all"
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop(role, service, prog_name = service)
|
54
|
+
puts "STOPING #{role} role (service: #{service}, program_name: #{prog_name})"
|
55
|
+
run "monit -g #{role} unmonitor all"
|
56
|
+
if (system("pidof -x #{prog_name}"))
|
57
|
+
result = run("sh /etc/init.d/#{service} stop")
|
58
|
+
end
|
59
|
+
# make start script non-executable in case of reboot
|
60
|
+
run("chmod a-x /etc/init.d/#{service}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def run(cmd)
|
64
|
+
result = system(cmd)
|
65
|
+
puts("*****ERROR: #{cmd} returned #{$?}") unless result
|
66
|
+
end
|
67
|
+
|
68
|
+
def sudo(cmd)
|
69
|
+
run("sudo #{cmd}")
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_address_metadata(type)
|
73
|
+
address = Net::HTTP.get('169.254.169.254', "/2007-08-29/meta-data/#{type}").strip
|
74
|
+
raise "couldn't get instance data: #{type}" unless address =~ /\A\d+\.\d+\.\d+\.\d+\Z/
|
75
|
+
# puts "#{type}: #{address}"
|
76
|
+
return address
|
77
|
+
end
|
78
|
+
|
79
|
+
def resolve(hostname)
|
80
|
+
address = IPSocket.getaddress(hostname).strip
|
81
|
+
if address == local_address || address == public_address
|
82
|
+
"127.0.0.1"
|
83
|
+
else
|
84
|
+
address
|
85
|
+
end
|
86
|
+
rescue Exception => e
|
87
|
+
puts "couldn't resolve hostname '#{hostname}'"
|
88
|
+
raise e
|
89
|
+
end
|
90
|
+
|
91
|
+
def resolve_all_addresses(original)
|
92
|
+
resolved = {}
|
93
|
+
original.each do |rolename, hostnames|
|
94
|
+
resolved[rolename] = hostnames.map{|hostname| resolve(hostname)} if hostnames
|
95
|
+
end
|
96
|
+
resolved
|
97
|
+
end
|
98
|
+
|
99
|
+
def in_role?(role)
|
100
|
+
return false unless roles[role]
|
101
|
+
return roles[role].include?("127.0.0.1")
|
102
|
+
end
|
103
|
+
#to provide deprecated usage
|
104
|
+
alias :in_role :in_role?
|
105
|
+
|
106
|
+
|
107
|
+
def add_etc_hosts_entry(entry_name, entry_addr)
|
108
|
+
host_file = "/etc/hosts"
|
109
|
+
run("cp #{host_file}.original #{host_file}") unless File.exists?("#{host_file}.original")
|
110
|
+
hosts = File.read(host_file)
|
111
|
+
unless hosts =~ /\s+#{entry_name}/
|
112
|
+
puts "adding '#{entry_addr}\t#{entry_name}' to /etc/hosts"
|
113
|
+
hosts << "\n#{entry_addr}\t#{entry_name}\n"
|
114
|
+
File.open(host_file, 'w') {|f| f.write(hosts) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def web_starting_port
|
119
|
+
mongrel_config['port'].to_i rescue 8000
|
120
|
+
end
|
121
|
+
|
122
|
+
def web_num_instances
|
123
|
+
mongrel_config['servers'].to_i rescue 6
|
124
|
+
end
|
125
|
+
|
126
|
+
def web_port_range
|
127
|
+
(web_starting_port..(web_starting_port + web_num_instances-1))
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def mongrel_config
|
133
|
+
@mongrel_config ||= YAML::load_file(MONGREL_CONF_FILE)
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# This file is part of EC2 on Rails.
|
2
|
+
# http://rubyforge.org/projects/ec2onrails/
|
3
|
+
#
|
4
|
+
# Copyright 2007 Paul Dowman, http://pauldowman.com/
|
5
|
+
#
|
6
|
+
# EC2 on Rails is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# EC2 on Rails is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'aws/s3'
|
21
|
+
require 'yaml'
|
22
|
+
require 'erb'
|
23
|
+
require 'fileutils'
|
24
|
+
require "#{File.dirname(__FILE__)}/utils"
|
25
|
+
|
26
|
+
module Ec2onrails
|
27
|
+
class S3Helper
|
28
|
+
|
29
|
+
DEFAULT_CONFIG_FILE = "/mnt/app/current/config/s3.yml"
|
30
|
+
|
31
|
+
# make attributes available for specs
|
32
|
+
attr_accessor :bucket
|
33
|
+
attr_accessor :dir
|
34
|
+
attr_accessor :config_file
|
35
|
+
attr_accessor :rails_env
|
36
|
+
attr_accessor :aws_access_key
|
37
|
+
attr_accessor :aws_secret_access_key
|
38
|
+
attr_accessor :bucket
|
39
|
+
|
40
|
+
def initialize(bucket, dir, config_file = DEFAULT_CONFIG_FILE, rails_env = Utils.rails_env)
|
41
|
+
@dir = dir
|
42
|
+
@config_file = config_file
|
43
|
+
@rails_env = rails_env
|
44
|
+
load_s3_config
|
45
|
+
@bucket = bucket || "#{@bucket_base_name}-#{Ec2onrails::Utils.hostname}"
|
46
|
+
AWS::S3::Base.establish_connection!(:access_key_id => @aws_access_key, :secret_access_key => @aws_secret_access_key, :use_ssl => true)
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_s3_config
|
50
|
+
if File.exists?(@config_file)
|
51
|
+
s3_config = YAML::load(ERB.new(File.read(@config_file)).result)
|
52
|
+
|
53
|
+
# try to load the section for the current RAILS_ENV
|
54
|
+
section = s3_config[@rails_env]
|
55
|
+
if section.nil?
|
56
|
+
# fall back to keys at the root of the tree
|
57
|
+
section = s3_config
|
58
|
+
end
|
59
|
+
|
60
|
+
@aws_access_key = section['aws_access_key']
|
61
|
+
@aws_secret_access_key = section['aws_secret_access_key']
|
62
|
+
@bucket_base_name = section['bucket_base_name']
|
63
|
+
else
|
64
|
+
if !File.exists?('/mnt/aws-config/config')
|
65
|
+
raise "Can't find either #{@config_file} or /mnt/aws-config/config"
|
66
|
+
end
|
67
|
+
@aws_access_key = get_bash_config('AWS_ACCESS_KEY_ID')
|
68
|
+
@aws_secret_access_key = get_bash_config('AWS_SECRET_ACCESS_KEY')
|
69
|
+
@bucket_base_name = get_bash_config('BUCKET_BASE_NAME')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_bucket
|
74
|
+
retries = 0
|
75
|
+
begin
|
76
|
+
AWS::S3::Bucket.find(@bucket)
|
77
|
+
rescue AWS::S3::NoSuchBucket
|
78
|
+
AWS::S3::Bucket.create(@bucket)
|
79
|
+
sleep 1 # If we try to use the bucket too quickly sometimes it's not found
|
80
|
+
retry if (retries += 1) < 15
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def store_file(file)
|
85
|
+
create_bucket
|
86
|
+
AWS::S3::S3Object.store(s3_key(file), open(file), @bucket)
|
87
|
+
end
|
88
|
+
|
89
|
+
def retrieve_file(file)
|
90
|
+
key = s3_key(file)
|
91
|
+
AWS::S3::S3Object.find(key, @bucket)
|
92
|
+
open(file, 'w') do |f|
|
93
|
+
AWS::S3::S3Object.stream(key, @bucket) do |chunk|
|
94
|
+
f.write chunk
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def list_keys(filename_prefix)
|
100
|
+
prefix = @dir ? "#{@dir}/#{filename_prefix}" : filename_prefix
|
101
|
+
AWS::S3::Bucket.objects(@bucket, :prefix => prefix).collect{|obj| obj.key}
|
102
|
+
end
|
103
|
+
|
104
|
+
def retrieve_files(filename_prefix, local_dir)
|
105
|
+
list_keys(filename_prefix).each do |k|
|
106
|
+
file = "#{local_dir}/#{File.basename(k)}"
|
107
|
+
retrieve_file(file)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def delete_files(filename_prefix)
|
112
|
+
list_keys(filename_prefix).each do |k|
|
113
|
+
AWS::S3::S3Object.delete(k, @bucket)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def s3_key(file)
|
118
|
+
@dir ? "#{@dir}/#{File.basename(file)}" : File.basename(file)
|
119
|
+
end
|
120
|
+
|
121
|
+
# load an env value from the shared config file
|
122
|
+
def get_bash_config(name)
|
123
|
+
`bash -c 'source /mnt/aws-config/config; echo $#{name}'`.strip
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Ec2onrails
|
2
|
+
module Utils
|
3
|
+
def self.run(command)
|
4
|
+
result = system command
|
5
|
+
raise("error: #{$?}") unless result
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.rails_env
|
9
|
+
`/usr/local/ec2onrails/bin/rails_env`.strip
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.hostname
|
13
|
+
`hostname -s`.strip
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
# from ini gem, version 0.1.1
|
2
|
+
# modified to allow keys with no value to be passed in (search for MODIFIED)
|
3
|
+
#
|
4
|
+
# This class represents the INI file and can be used to parse, modify,
|
5
|
+
# and write INI files.
|
6
|
+
#
|
7
|
+
class Ini
|
8
|
+
|
9
|
+
# :stopdoc:
|
10
|
+
class Error < StandardError; end
|
11
|
+
# :startdoc:
|
12
|
+
|
13
|
+
#
|
14
|
+
# call-seq:
|
15
|
+
# IniFile.load( filename )
|
16
|
+
# IniFile.load( filename, options )
|
17
|
+
#
|
18
|
+
# Open the given _filename_ and load the contetns of the INI file.
|
19
|
+
# The following _options_ can be passed to this method:
|
20
|
+
#
|
21
|
+
# :comment => ';' The line comment character(s)
|
22
|
+
# :parameter => '=' The parameter / value separator
|
23
|
+
#
|
24
|
+
def self.load( filename, opts = {} )
|
25
|
+
new(filename, opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# call-seq:
|
30
|
+
# IniFile.new( filename )
|
31
|
+
# IniFile.new( filename, options )
|
32
|
+
#
|
33
|
+
# Create a new INI file using the given _filename_. If _filename_
|
34
|
+
# exists and is a regular file, then its contents will be parsed.
|
35
|
+
# The following _options_ can be passed to this method:
|
36
|
+
#
|
37
|
+
# :comment => ';' The line comment character(s)
|
38
|
+
# :parameter => '=' The parameter / value separator
|
39
|
+
#
|
40
|
+
def initialize( filename, opts = {} )
|
41
|
+
@fn = filename
|
42
|
+
@comment = opts[:comment] || ';'
|
43
|
+
@param = opts[:parameter] || '='
|
44
|
+
@ini = Hash.new {|h,k| h[k] = Hash.new}
|
45
|
+
|
46
|
+
@rgxp_comment = %r/\A\s*\z|\A\s*[#{@comment}]/
|
47
|
+
@rgxp_section = %r/\A\s*\[([^\]]+)\]/o
|
48
|
+
#MODIFIED: added #{@param}?... that question mark means that we will match rows
|
49
|
+
# with just a key, but no value
|
50
|
+
@rgxp_param = %r/\A([^#{@param}]+)#{@param}?(.*)\z/
|
51
|
+
|
52
|
+
parse
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# call-seq:
|
57
|
+
# write
|
58
|
+
# write( filename )
|
59
|
+
#
|
60
|
+
# Write the INI file contents to the filesystem. The given _filename_
|
61
|
+
# will be used to write the file. If _filename_ is not given, then the
|
62
|
+
# named used when constructing this object will be used.
|
63
|
+
#
|
64
|
+
def write( filename = nil )
|
65
|
+
@fn = filename unless filename.nil?
|
66
|
+
|
67
|
+
::File.open(@fn, 'w') do |f|
|
68
|
+
@ini.each do |section,hash|
|
69
|
+
f.puts "[#{section}]"
|
70
|
+
#MODIFY: do not print out the '=' if there is no value... PLUS remove spaces around the '='
|
71
|
+
hash.each {|param,val| f.puts val.nil? ? param : "#{param}#{@param}#{val}"}
|
72
|
+
f.puts
|
73
|
+
end
|
74
|
+
end
|
75
|
+
self
|
76
|
+
end
|
77
|
+
alias :save :write
|
78
|
+
|
79
|
+
#
|
80
|
+
# call-seq:
|
81
|
+
# each {|section, parameter, value| block}
|
82
|
+
#
|
83
|
+
# Yield each _section_, _parameter_, _value_ in turn to the given
|
84
|
+
# _block_. The method returns immediately if no block is supplied.
|
85
|
+
#
|
86
|
+
def each
|
87
|
+
return unless block_given?
|
88
|
+
@ini.each do |section,hash|
|
89
|
+
hash.each do |param,val|
|
90
|
+
yield section, param, val
|
91
|
+
end
|
92
|
+
end
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# call-seq:
|
98
|
+
# each_section {|section| block}
|
99
|
+
#
|
100
|
+
# Yield each _section_ in turn to the given _block_. The method returns
|
101
|
+
# immediately if no block is supplied.
|
102
|
+
#
|
103
|
+
def each_section
|
104
|
+
return unless block_given?
|
105
|
+
@ini.each_key {|section| yield section}
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# call-seq:
|
111
|
+
# delete_section( section )
|
112
|
+
#
|
113
|
+
# Deletes the named _section_ from the INI file. Returns the
|
114
|
+
# parameter / value pairs if the section exists in the INI file. Otherwise,
|
115
|
+
# returns +nil+.
|
116
|
+
#
|
117
|
+
def delete_section( section )
|
118
|
+
@ini.delete section.to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# call-seq:
|
123
|
+
# ini_file[section]
|
124
|
+
#
|
125
|
+
# Get the hash of parameter/value pairs for the given _section_. If the
|
126
|
+
# _section_ hash does not exist it will be created.
|
127
|
+
#
|
128
|
+
def []( section )
|
129
|
+
return nil if section.nil?
|
130
|
+
@ini[section.to_s]
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# call-seq:
|
135
|
+
# has_section?( section )
|
136
|
+
#
|
137
|
+
# Returns +true+ if the named _section_ exists in the INI file.
|
138
|
+
#
|
139
|
+
def has_section?( section )
|
140
|
+
@ini.has_key? section.to_s
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# call-seq:
|
145
|
+
# sections
|
146
|
+
#
|
147
|
+
# Returns an array of the section names.
|
148
|
+
#
|
149
|
+
def sections
|
150
|
+
@ini.keys
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# call-seq:
|
155
|
+
# freeze
|
156
|
+
#
|
157
|
+
# Freeze the state of the +IniFile+ object. Any attempts to change the
|
158
|
+
# object will raise an error.
|
159
|
+
#
|
160
|
+
def freeze
|
161
|
+
super
|
162
|
+
@ini.each_value {|h| h.freeze}
|
163
|
+
@ini.freeze
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# call-seq:
|
169
|
+
# taint
|
170
|
+
#
|
171
|
+
# Marks the INI file as tainted -- this will traverse each section marking
|
172
|
+
# each section as tainted as well.
|
173
|
+
#
|
174
|
+
def taint
|
175
|
+
super
|
176
|
+
@ini.each_value {|h| h.taint}
|
177
|
+
@ini.taint
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# call-seq:
|
183
|
+
# dup
|
184
|
+
#
|
185
|
+
# Produces a duplicate of this INI file. The duplicate is independent of the
|
186
|
+
# original -- i.e. the duplicate can be modified without changing the
|
187
|
+
# orgiinal. The tainted state of the original is copied to the duplicate.
|
188
|
+
#
|
189
|
+
def dup
|
190
|
+
other = super
|
191
|
+
other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
|
192
|
+
@ini.each_pair {|s,h| other[s].merge! h}
|
193
|
+
other.taint if self.tainted?
|
194
|
+
other
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# call-seq:
|
199
|
+
# clone
|
200
|
+
#
|
201
|
+
# Produces a duplicate of this INI file. The duplicate is independent of the
|
202
|
+
# original -- i.e. the duplicate can be modified without changing the
|
203
|
+
# orgiinal. The tainted state and the frozen state of the original is copied
|
204
|
+
# to the duplicate.
|
205
|
+
#
|
206
|
+
def clone
|
207
|
+
other = dup
|
208
|
+
other.freeze if self.frozen?
|
209
|
+
other
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# call-seq:
|
214
|
+
# eql?( other )
|
215
|
+
#
|
216
|
+
# Returns +true+ if the _other_ object is equivalent to this INI file. For
|
217
|
+
# two INI files to be equivalent, they must have the same sections with the
|
218
|
+
# same parameter / value pairs in each section.
|
219
|
+
#
|
220
|
+
def eql?( other )
|
221
|
+
return true if equal? other
|
222
|
+
return false unless other.instance_of? self.class
|
223
|
+
@ini == other.instance_variable_get(:@ini)
|
224
|
+
end
|
225
|
+
alias :== :eql?
|
226
|
+
|
227
|
+
|
228
|
+
private
|
229
|
+
#
|
230
|
+
# call-seq
|
231
|
+
# parse
|
232
|
+
#
|
233
|
+
# Parse the ini file contents.
|
234
|
+
#
|
235
|
+
def parse
|
236
|
+
return unless ::Kernel.test ?f, @fn
|
237
|
+
section = nil
|
238
|
+
|
239
|
+
::File.open(@fn, 'r') do |f|
|
240
|
+
while line = f.gets
|
241
|
+
line = line.chomp
|
242
|
+
|
243
|
+
case line
|
244
|
+
# ignore blank lines and comment lines
|
245
|
+
when @rgxp_comment: next
|
246
|
+
|
247
|
+
# this is a section declaration
|
248
|
+
when @rgxp_section: section = @ini[$1.strip]
|
249
|
+
|
250
|
+
# otherwise we have a parameter
|
251
|
+
when @rgxp_param
|
252
|
+
begin
|
253
|
+
#MODIFY: store no value as a nil instead of a blank
|
254
|
+
section[$1.strip] = $2.strip.size == 0 ? nil : $2.strip
|
255
|
+
rescue NoMethodError
|
256
|
+
raise Error, "parameter encountered before first section"
|
257
|
+
end
|
258
|
+
|
259
|
+
else
|
260
|
+
raise Error, "could not parse line '#{line}"
|
261
|
+
end
|
262
|
+
end # while
|
263
|
+
end # File.open
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
# EOF
|