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.
Files changed (170) hide show
  1. data/{History.txt → CHANGELOG} +0 -0
  2. data/{COPYING.txt → COPYING} +0 -0
  3. data/Manifest +161 -0
  4. data/{website/index.txt → README.textile} +33 -5
  5. data/Rakefile +36 -4
  6. data/TODO +91 -0
  7. data/ec2onrails.gemspec +279 -0
  8. data/examples/Capfile +3 -0
  9. data/examples/deploy.rb +88 -0
  10. data/examples/s3.yml +9 -0
  11. data/lib/ec2onrails/capistrano_utils.rb +0 -11
  12. data/lib/ec2onrails/recipes.rb +165 -59
  13. data/lib/ec2onrails/version.rb +1 -1
  14. data/server/build-ec2onrails.sh +44 -0
  15. data/server/files/etc/aliases +5 -0
  16. data/server/files/etc/aliases.db +0 -0
  17. data/server/files/etc/apache2/apache2.conf +295 -0
  18. data/server/files/etc/apache2/conf.d/app.proxy_cluster.conf +7 -0
  19. data/server/files/etc/apache2/conf.d/app.proxy_frontend.conf +10 -0
  20. data/server/files/etc/apache2/mods-available/proxy.conf +18 -0
  21. data/server/files/etc/apache2/sites-available/app.common +56 -0
  22. data/server/files/etc/apache2/sites-available/app.custom +0 -0
  23. data/server/files/etc/apache2/sites-available/default +14 -0
  24. data/server/files/etc/apache2/sites-available/default-ssl +18 -0
  25. data/server/files/etc/cron.d/backup_app_db_to_s3 +6 -0
  26. data/server/files/etc/cron.daily/app +9 -0
  27. data/server/files/etc/cron.daily/logrotate_post +19 -0
  28. data/server/files/etc/cron.hourly/app +10 -0
  29. data/server/files/etc/cron.monthly/app +10 -0
  30. data/server/files/etc/cron.weekly/app +10 -0
  31. data/server/files/etc/ec2onrails/balancer_members +6 -0
  32. data/server/files/etc/ec2onrails/roles.yml +5 -0
  33. data/server/files/etc/environment +2 -0
  34. data/server/files/etc/event.d/monit +13 -0
  35. data/server/files/etc/init.d/ec2-every-startup +29 -0
  36. data/server/files/etc/init.d/ec2-first-startup +36 -0
  37. data/server/files/etc/init.d/mongrel +91 -0
  38. data/server/files/etc/init.d/nginx +78 -0
  39. data/server/files/etc/init.d/set_roles +3 -0
  40. data/server/files/etc/logrotate.d/apache2 +16 -0
  41. data/server/files/etc/logrotate.d/mongrel +11 -0
  42. data/server/files/etc/logrotate.d/nginx +11 -0
  43. data/server/files/etc/memcached.conf +47 -0
  44. data/server/files/etc/mongrel_cluster/app.yml +9 -0
  45. data/server/files/etc/monit/README +5 -0
  46. data/server/files/etc/monit/app.monitrc.erb +13 -0
  47. data/server/files/etc/monit/db_primary.monitrc.erb +10 -0
  48. data/server/files/etc/monit/memcache.monitrc +8 -0
  49. data/server/files/etc/monit/monitrc +12 -0
  50. data/server/files/etc/monit/system.monitrc +15 -0
  51. data/server/files/etc/monit/web.monitrc.erb +23 -0
  52. data/server/files/etc/motd.tail +13 -0
  53. data/server/files/etc/mysql/my.cnf +149 -0
  54. data/server/files/etc/nginx/nginx.conf +296 -0
  55. data/server/files/etc/postfix/main.cf +4 -0
  56. data/server/files/etc/rc0.d/K10mongrel +1 -0
  57. data/server/files/etc/rc1.d/K10mongrel +1 -0
  58. data/server/files/etc/rc2.d/S90mongrel +1 -0
  59. data/server/files/etc/rc3.d/S90mongrel +1 -0
  60. data/server/files/etc/rc4.d/S90mongrel +1 -0
  61. data/server/files/etc/rc5.d/S90mongrel +1 -0
  62. data/server/files/etc/rc6.d/K10mongrel +1 -0
  63. data/server/files/etc/rcS.d/S91ec2-first-startup +1 -0
  64. data/server/files/etc/rcS.d/S92ec2-every-startup +1 -0
  65. data/server/files/etc/rcS.d/S99set_roles +1 -0
  66. data/server/files/etc/ssh/sshd_config +94 -0
  67. data/server/files/etc/sudoers +1 -0
  68. data/server/files/etc/sudoers.full_access +26 -0
  69. data/server/files/etc/sudoers.restricted_access +28 -0
  70. data/server/files/etc/syslog.conf +69 -0
  71. data/server/files/usr/local/ec2onrails/COPYING +339 -0
  72. data/server/files/usr/local/ec2onrails/bin/archive_file.rb +44 -0
  73. data/server/files/usr/local/ec2onrails/bin/backup_app_db.rb +68 -0
  74. data/server/files/usr/local/ec2onrails/bin/init_services.rb +57 -0
  75. data/server/files/usr/local/ec2onrails/bin/mongrel_start +8 -0
  76. data/server/files/usr/local/ec2onrails/bin/mongrel_stop +8 -0
  77. data/server/files/usr/local/ec2onrails/bin/optimize_mysql.rb +339 -0
  78. data/server/files/usr/local/ec2onrails/bin/rails_env +35 -0
  79. data/server/files/usr/local/ec2onrails/bin/rebundle.sh +70 -0
  80. data/server/files/usr/local/ec2onrails/bin/restore_app_db.rb +58 -0
  81. data/server/files/usr/local/ec2onrails/bin/set_rails_env +40 -0
  82. data/server/files/usr/local/ec2onrails/bin/set_roles.rb +76 -0
  83. data/server/files/usr/local/ec2onrails/bin/setup_web_proxy.rb +106 -0
  84. data/server/files/usr/local/ec2onrails/config +30 -0
  85. data/server/files/usr/local/ec2onrails/lib/mysql_helper.rb +82 -0
  86. data/server/files/usr/local/ec2onrails/lib/roles_helper.rb +137 -0
  87. data/server/files/usr/local/ec2onrails/lib/s3_helper.rb +126 -0
  88. data/server/files/usr/local/ec2onrails/lib/utils.rb +16 -0
  89. data/server/files/usr/local/ec2onrails/lib/vendor/ini.rb +268 -0
  90. data/server/files/usr/local/ec2onrails/startup-scripts/every-startup/get-hostname.sh +27 -0
  91. data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/README +5 -0
  92. data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/create-dirs.sh +42 -0
  93. data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/generate-default-web-cert-and-key.sh +49 -0
  94. data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/misc.sh +27 -0
  95. data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/prepare-mysql-data-dir.sh +24 -0
  96. data/server/files/usr/local/ec2onrails/startup-scripts/first-startup/setup-credentials.sh +29 -0
  97. data/server/rakefile.rb +222 -0
  98. data/test/autobench.conf +60 -0
  99. data/test/spec/lib/s3_helper_spec.rb +134 -0
  100. data/test/spec/lib/s3_old.yml +3 -0
  101. data/test/spec/test_files/test1 +0 -0
  102. data/test/spec/test_files/test2 +0 -0
  103. data/test/test_app/Capfile +3 -0
  104. data/test/test_app/README +182 -0
  105. data/test/test_app/Rakefile +10 -0
  106. data/test/test_app/app/controllers/application.rb +7 -0
  107. data/test/test_app/app/controllers/db_fast_controller.rb +6 -0
  108. data/test/test_app/app/controllers/fast_controller.rb +5 -0
  109. data/test/test_app/app/controllers/slow_controller.rb +6 -0
  110. data/test/test_app/app/controllers/very_slow_controller.rb +6 -0
  111. data/test/test_app/app/helpers/application_helper.rb +3 -0
  112. data/test/test_app/app/helpers/db_fast_helper.rb +2 -0
  113. data/test/test_app/app/helpers/fast_helper.rb +2 -0
  114. data/test/test_app/app/helpers/slow_helper.rb +2 -0
  115. data/test/test_app/app/helpers/very_slow_helper.rb +2 -0
  116. data/test/test_app/config/boot.rb +109 -0
  117. data/test/test_app/config/database.yml +19 -0
  118. data/test/test_app/config/deploy.rb +21 -0
  119. data/test/test_app/config/environment.rb +60 -0
  120. data/test/test_app/config/environments/development.rb +21 -0
  121. data/test/test_app/config/environments/production.rb +18 -0
  122. data/test/test_app/config/environments/test.rb +19 -0
  123. data/test/test_app/config/routes.rb +27 -0
  124. data/test/test_app/db/schema.rb +7 -0
  125. data/test/test_app/doc/README_FOR_APP +2 -0
  126. data/test/test_app/public/404.html +30 -0
  127. data/test/test_app/public/500.html +30 -0
  128. data/test/test_app/public/dispatch.cgi +10 -0
  129. data/test/test_app/public/dispatch.fcgi +24 -0
  130. data/test/test_app/public/dispatch.rb +10 -0
  131. data/test/test_app/public/favicon.ico +0 -0
  132. data/test/test_app/public/images/rails.png +0 -0
  133. data/test/test_app/public/javascripts/application.js +2 -0
  134. data/test/test_app/public/javascripts/controls.js +963 -0
  135. data/test/test_app/public/javascripts/dragdrop.js +972 -0
  136. data/test/test_app/public/javascripts/effects.js +1120 -0
  137. data/test/test_app/public/javascripts/prototype.js +4225 -0
  138. data/test/test_app/public/robots.txt +1 -0
  139. data/test/test_app/script/about +3 -0
  140. data/test/test_app/script/breakpointer +3 -0
  141. data/test/test_app/script/console +3 -0
  142. data/test/test_app/script/destroy +3 -0
  143. data/test/test_app/script/generate +3 -0
  144. data/test/test_app/script/performance/benchmarker +3 -0
  145. data/test/test_app/script/performance/profiler +3 -0
  146. data/test/test_app/script/performance/request +3 -0
  147. data/test/test_app/script/plugin +3 -0
  148. data/test/test_app/script/process/inspector +3 -0
  149. data/test/test_app/script/process/reaper +3 -0
  150. data/test/test_app/script/process/spawner +3 -0
  151. data/test/test_app/script/runner +3 -0
  152. data/test/test_app/script/server +3 -0
  153. data/test/test_app/test/functional/db_fast_controller_test.rb +18 -0
  154. data/test/test_app/test/functional/fast_controller_test.rb +18 -0
  155. data/test/test_app/test/functional/slow_controller_test.rb +18 -0
  156. data/test/test_app/test/functional/very_slow_controller_test.rb +18 -0
  157. metadata +193 -36
  158. data/Manifest.txt +0 -25
  159. data/README.txt +0 -1
  160. data/config/hoe.rb +0 -70
  161. data/config/requirements.rb +0 -17
  162. data/script/destroy +0 -14
  163. data/script/generate +0 -14
  164. data/script/txt2html +0 -74
  165. data/tasks/deployment.rake +0 -27
  166. data/tasks/environment.rake +0 -7
  167. data/tasks/website.rake +0 -17
  168. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  169. data/website/stylesheets/screen.css +0 -144
  170. 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