rubyrep 1.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.
Files changed (140) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +137 -0
  4. data/README.txt +37 -0
  5. data/Rakefile +30 -0
  6. data/bin/rubyrep +8 -0
  7. data/config/hoe.rb +72 -0
  8. data/config/mysql_config.rb +25 -0
  9. data/config/postgres_config.rb +21 -0
  10. data/config/proxied_test_config.rb +14 -0
  11. data/config/redmine_config.rb +17 -0
  12. data/config/rep_config.rb +20 -0
  13. data/config/requirements.rb +32 -0
  14. data/config/test_config.rb +20 -0
  15. data/lib/rubyrep/base_runner.rb +195 -0
  16. data/lib/rubyrep/command_runner.rb +144 -0
  17. data/lib/rubyrep/committers/buffered_committer.rb +140 -0
  18. data/lib/rubyrep/committers/committers.rb +146 -0
  19. data/lib/rubyrep/configuration.rb +240 -0
  20. data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
  21. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
  22. data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
  23. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
  24. data/lib/rubyrep/database_proxy.rb +52 -0
  25. data/lib/rubyrep/direct_table_scan.rb +75 -0
  26. data/lib/rubyrep/generate_runner.rb +105 -0
  27. data/lib/rubyrep/initializer.rb +39 -0
  28. data/lib/rubyrep/logged_change.rb +326 -0
  29. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  30. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  31. data/lib/rubyrep/proxy_connection.rb +318 -0
  32. data/lib/rubyrep/proxy_cursor.rb +44 -0
  33. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  34. data/lib/rubyrep/proxy_runner.rb +89 -0
  35. data/lib/rubyrep/replication_difference.rb +91 -0
  36. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  37. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
  38. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  39. data/lib/rubyrep/replication_helper.rb +104 -0
  40. data/lib/rubyrep/replication_initializer.rb +307 -0
  41. data/lib/rubyrep/replication_run.rb +48 -0
  42. data/lib/rubyrep/replication_runner.rb +138 -0
  43. data/lib/rubyrep/replicators/replicators.rb +37 -0
  44. data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
  45. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  46. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  47. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  48. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  49. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  50. data/lib/rubyrep/scan_runner.rb +25 -0
  51. data/lib/rubyrep/session.rb +177 -0
  52. data/lib/rubyrep/sync_helper.rb +111 -0
  53. data/lib/rubyrep/sync_runner.rb +31 -0
  54. data/lib/rubyrep/syncers/syncers.rb +112 -0
  55. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  56. data/lib/rubyrep/table_scan.rb +54 -0
  57. data/lib/rubyrep/table_scan_helper.rb +38 -0
  58. data/lib/rubyrep/table_sorter.rb +70 -0
  59. data/lib/rubyrep/table_spec_resolver.rb +136 -0
  60. data/lib/rubyrep/table_sync.rb +68 -0
  61. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  62. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  63. data/lib/rubyrep/uninstall_runner.rb +92 -0
  64. data/lib/rubyrep/version.rb +9 -0
  65. data/lib/rubyrep.rb +68 -0
  66. data/script/destroy +14 -0
  67. data/script/generate +14 -0
  68. data/script/txt2html +74 -0
  69. data/setup.rb +1585 -0
  70. data/sims/performance/big_rep_spec.rb +100 -0
  71. data/sims/performance/big_scan_spec.rb +57 -0
  72. data/sims/performance/big_sync_spec.rb +141 -0
  73. data/sims/performance/performance.rake +228 -0
  74. data/sims/sim_helper.rb +24 -0
  75. data/spec/base_runner_spec.rb +218 -0
  76. data/spec/buffered_committer_spec.rb +271 -0
  77. data/spec/command_runner_spec.rb +145 -0
  78. data/spec/committers_spec.rb +174 -0
  79. data/spec/configuration_spec.rb +198 -0
  80. data/spec/connection_extender_interface_spec.rb +138 -0
  81. data/spec/connection_extenders_registration_spec.rb +129 -0
  82. data/spec/database_proxy_spec.rb +48 -0
  83. data/spec/database_rake_spec.rb +40 -0
  84. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  85. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  86. data/spec/direct_table_scan_spec.rb +61 -0
  87. data/spec/generate_runner_spec.rb +84 -0
  88. data/spec/initializer_spec.rb +46 -0
  89. data/spec/logged_change_spec.rb +480 -0
  90. data/spec/postgresql_replication_spec.rb +48 -0
  91. data/spec/postgresql_support_spec.rb +57 -0
  92. data/spec/progress_bar_spec.rb +77 -0
  93. data/spec/proxied_table_scan_spec.rb +151 -0
  94. data/spec/proxy_block_cursor_spec.rb +197 -0
  95. data/spec/proxy_connection_spec.rb +399 -0
  96. data/spec/proxy_cursor_spec.rb +56 -0
  97. data/spec/proxy_row_cursor_spec.rb +66 -0
  98. data/spec/proxy_runner_spec.rb +70 -0
  99. data/spec/replication_difference_spec.rb +160 -0
  100. data/spec/replication_extender_interface_spec.rb +365 -0
  101. data/spec/replication_extenders_spec.rb +32 -0
  102. data/spec/replication_helper_spec.rb +121 -0
  103. data/spec/replication_initializer_spec.rb +477 -0
  104. data/spec/replication_run_spec.rb +166 -0
  105. data/spec/replication_runner_spec.rb +213 -0
  106. data/spec/replicators_spec.rb +31 -0
  107. data/spec/rubyrep_spec.rb +8 -0
  108. data/spec/scan_detail_reporter_spec.rb +119 -0
  109. data/spec/scan_progress_printers_spec.rb +68 -0
  110. data/spec/scan_report_printers_spec.rb +67 -0
  111. data/spec/scan_runner_spec.rb +50 -0
  112. data/spec/scan_summary_reporter_spec.rb +61 -0
  113. data/spec/session_spec.rb +212 -0
  114. data/spec/spec.opts +1 -0
  115. data/spec/spec_helper.rb +295 -0
  116. data/spec/sync_helper_spec.rb +157 -0
  117. data/spec/sync_runner_spec.rb +78 -0
  118. data/spec/syncers_spec.rb +171 -0
  119. data/spec/table_scan_helper_spec.rb +29 -0
  120. data/spec/table_scan_spec.rb +49 -0
  121. data/spec/table_sorter_spec.rb +31 -0
  122. data/spec/table_spec_resolver_spec.rb +102 -0
  123. data/spec/table_sync_spec.rb +84 -0
  124. data/spec/trigger_mode_switcher_spec.rb +83 -0
  125. data/spec/two_way_replicator_spec.rb +551 -0
  126. data/spec/two_way_syncer_spec.rb +256 -0
  127. data/spec/type_casting_cursor_spec.rb +50 -0
  128. data/spec/uninstall_runner_spec.rb +86 -0
  129. data/tasks/database.rake +439 -0
  130. data/tasks/deployment.rake +29 -0
  131. data/tasks/environment.rake +9 -0
  132. data/tasks/java.rake +37 -0
  133. data/tasks/redmine_test.rake +47 -0
  134. data/tasks/rspec.rake +68 -0
  135. data/tasks/rubyrep.tailor +18 -0
  136. data/tasks/stats.rake +19 -0
  137. data/tasks/task_helper.rb +20 -0
  138. data.tar.gz.sig +0 -0
  139. metadata +243 -0
  140. metadata.gz.sig +0 -0
@@ -0,0 +1,261 @@
1
+ require 'time'
2
+
3
+ # A cursor to iterate over the records returned by select_cursor.
4
+ # Only one row is kept in memory at a time.
5
+ class PGresult
6
+ # Returns true if there are more rows to read.
7
+ def next?
8
+ @current_row_num ||= 0
9
+ @num_rows ||= self.ntuples()
10
+ @current_row_num < @num_rows
11
+ end
12
+
13
+ # Returns the row as a column => value hash and moves the cursor to the next row.
14
+ def next_row
15
+ raise("no more rows available") unless next?
16
+ row = {}
17
+ @fields ||= self.fields
18
+ @fields.each_with_index do |field, field_index|
19
+ if self.getisnull(@current_row_num, field_index)
20
+ value = nil
21
+ else
22
+ value = self.getvalue @current_row_num, field_index
23
+ end
24
+
25
+ row[@fields[field_index]] = value
26
+ end
27
+ @current_row_num += 1
28
+ row
29
+ end
30
+ end
31
+
32
+ # Hack:
33
+ # For some reasons these methods were removed in Rails 2.2.2, thus breaking
34
+ # the binary and multi-lingual data loading.
35
+ # So here they are again.
36
+ module ActiveRecord
37
+ module ConnectionAdapters
38
+ # PostgreSQL-specific extensions to column definitions in a table.
39
+ class PostgreSQLColumn < Column #:nodoc:
40
+
41
+ # Escapes binary strings for bytea input to the database.
42
+ def self.string_to_binary(value)
43
+ if PGconn.respond_to?(:escape_bytea)
44
+ self.class.module_eval do
45
+ define_method(:string_to_binary) do |value|
46
+ PGconn.escape_bytea(value) if value
47
+ end
48
+ end
49
+ else
50
+ self.class.module_eval do
51
+ define_method(:string_to_binary) do |value|
52
+ if value
53
+ result = ''
54
+ value.each_byte { |c| result << sprintf('\\\\%03o', c) }
55
+ result
56
+ end
57
+ end
58
+ end
59
+ end
60
+ self.class.string_to_binary(value)
61
+ end
62
+
63
+ # Unescapes bytea output from a database to the binary string it represents.
64
+ def self.binary_to_string(value)
65
+ # In each case, check if the value actually is escaped PostgreSQL bytea output
66
+ # or an unescaped Active Record attribute that was just written.
67
+ if PGconn.respond_to?(:unescape_bytea)
68
+ self.class.module_eval do
69
+ define_method(:binary_to_string) do |value|
70
+ if value =~ /\\\d{3}/
71
+ PGconn.unescape_bytea(value)
72
+ else
73
+ value
74
+ end
75
+ end
76
+ end
77
+ else
78
+ self.class.module_eval do
79
+ define_method(:binary_to_string) do |value|
80
+ if value =~ /\\\d{3}/
81
+ result = ''
82
+ i, max = 0, value.size
83
+ while i < max
84
+ char = value[i]
85
+ if char == ?\\
86
+ if value[i+1] == ?\\
87
+ char = ?\\
88
+ i += 1
89
+ else
90
+ char = value[i+1..i+3].oct
91
+ i += 3
92
+ end
93
+ end
94
+ result << char
95
+ i += 1
96
+ end
97
+ result
98
+ else
99
+ value
100
+ end
101
+ end
102
+ end
103
+ end
104
+ self.class.binary_to_string(value)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ module RR
111
+ module ConnectionExtenders
112
+
113
+ # Fetches results from a PostgreSQL cursor object.
114
+ class PostgreSQLFetcher
115
+
116
+ # The current database connection
117
+ attr_accessor :connection
118
+
119
+ # Name of the cursor from which to fetch
120
+ attr_accessor :cursor_name
121
+
122
+ # Number of rows to be read at once
123
+ attr_accessor :row_buffer_size
124
+
125
+ # Creates a new fetcher.
126
+ # * +connection+: the current database connection
127
+ # * +cursor_name+: name of the cursor from which to fetch
128
+ # * +row_buffer_size+: number of records to read at once
129
+ def initialize(connection, cursor_name, row_buffer_size)
130
+ self.connection = connection
131
+ self.cursor_name = cursor_name
132
+ self.row_buffer_size = row_buffer_size
133
+ end
134
+
135
+ # Executes the specified SQL staements, returning the result
136
+ def execute(sql)
137
+ connection.execute sql
138
+ end
139
+
140
+ # Returns true if there are more rows to read.
141
+ def next?
142
+ @current_result ||= execute("FETCH FORWARD #{row_buffer_size} FROM #{cursor_name}")
143
+ @current_result.next?
144
+ end
145
+
146
+ # Returns the row as a column => value hash and moves the cursor to the next row.
147
+ def next_row
148
+ raise("no more rows available") unless next?
149
+ row = @current_result.next_row
150
+ unless @current_result.next?
151
+ @current_result.clear
152
+ @current_result = nil
153
+ end
154
+ row
155
+ end
156
+
157
+ # Closes the cursor and frees up all ressources
158
+ def clear
159
+ if @current_result
160
+ @current_result.clear
161
+ @current_result = nil
162
+ end
163
+ result = execute("CLOSE #{cursor_name}")
164
+ result.clear if result
165
+ end
166
+ end
167
+
168
+ # Provides various PostgreSQL specific functionality required by Rubyrep.
169
+ module PostgreSQLExtender
170
+ RR::ConnectionExtenders.register :postgresql => self
171
+
172
+ # Executes the given sql query with the otional name written in the
173
+ # ActiveRecord log file.
174
+ #
175
+ # :+row_buffer_size+ controls how many records are ready into memory at a
176
+ # time. Implemented using the PostgeSQL "DECLARE CURSOR" and "FETCH" constructs.
177
+ # This is necessary as the postgresql driver always reads the
178
+ # complete resultset into memory.
179
+ #
180
+ # Returns the results as a Cursor object supporting
181
+ # * next? - returns true if there are more rows to read
182
+ # * next_row - returns the row as a column => value hash and moves the cursor to the next row
183
+ # * clear - clearing the cursor (making allocated memory available for GC)
184
+ def select_cursor(sql, row_buffer_size = 1000)
185
+ cursor_name = "RR_#{Time.now.to_i}#{rand(1_000_000)}"
186
+ execute("DECLARE #{cursor_name} NO SCROLL CURSOR WITH HOLD FOR " + sql)
187
+ PostgreSQLFetcher.new(self, cursor_name, row_buffer_size)
188
+ end
189
+
190
+ # Returns an ordered list of primary key column names of the given table
191
+ def primary_key_names(table)
192
+ row = self.select_one(<<-end_sql)
193
+ SELECT relname
194
+ FROM pg_class
195
+ WHERE relname = '#{table}'
196
+ end_sql
197
+ if row.nil?
198
+ raise "table '#{table}' does not exist"
199
+ end
200
+
201
+ row = self.select_one(<<-end_sql)
202
+ SELECT cons.conkey
203
+ FROM pg_class rel
204
+ JOIN pg_constraint cons ON (rel.oid = cons.conrelid)
205
+ WHERE cons.contype = 'p' AND rel.relname = '#{table}'
206
+ end_sql
207
+ if row.nil?
208
+ return []
209
+ end
210
+ column_parray = row['conkey']
211
+
212
+ # Change a Postgres Array of attribute numbers
213
+ # (returned in String form, e. g.: "{1,2}") into an array of Integers
214
+ column_ids = column_parray.sub(/^\{(.*)\}$/,'\1').split(',').map {|a| a.to_i}
215
+
216
+ columns = {}
217
+ rows = self.select_all(<<-end_sql)
218
+ SELECT attnum, attname
219
+ FROM pg_class rel
220
+ JOIN pg_constraint cons ON (rel.oid = cons.conrelid)
221
+ JOIN pg_attribute attr ON (rel.oid = attr.attrelid and attr.attnum = any (cons.conkey))
222
+ WHERE cons.contype = 'p' AND rel.relname = '#{table}'
223
+ end_sql
224
+ sorted_columns = []
225
+ if not rows.nil?
226
+ rows.each() {|r| columns[r['attnum'].to_i] = r['attname']}
227
+ sorted_columns = column_ids.map {|column_id| columns[column_id]}
228
+ end
229
+ sorted_columns
230
+ end
231
+
232
+ # Returns for each given table, which other tables it references via
233
+ # foreign key constraints.
234
+ # * tables: an array of table names
235
+ # Returns: a hash with
236
+ # * key: name of the referencing table
237
+ # * value: an array of names of referenced tables
238
+ def referenced_tables(tables)
239
+ rows = self.select_all(<<-end_sql)
240
+ select distinct referencing.relname as referencing_table, referenced.relname as referenced_table
241
+ from pg_class referencing
242
+ left join pg_constraint on referencing.oid = pg_constraint.conrelid
243
+ left join pg_class referenced on pg_constraint.confrelid = referenced.oid
244
+ where referencing.relkind='r'
245
+ and referencing.relname in ('#{tables.join("', '")}')
246
+ end_sql
247
+ result = {}
248
+ rows.each do |row|
249
+ unless result.include? row['referencing_table']
250
+ result[row['referencing_table']] = []
251
+ end
252
+ if row['referenced_table'] != nil
253
+ result[row['referencing_table']] << row['referenced_table']
254
+ end
255
+ end
256
+ result
257
+ end
258
+ end
259
+ end
260
+ end
261
+
@@ -0,0 +1,52 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/..'
2
+
3
+ require 'drb'
4
+
5
+ require 'rubyrep'
6
+
7
+ module RR
8
+ # The proxy to a remote database connection
9
+ class DatabaseProxy
10
+
11
+ # Ensure that the proxy object always stays on server side and only remote
12
+ # references are returned to the client.
13
+ include DRbUndumped
14
+
15
+ # Default tcp port to listen on
16
+ DEFAULT_PORT = 9876
17
+
18
+ # A simple Hash to hold Session object
19
+ # Purpose: preventing them from being garbage collected when they are only referenced through Drb
20
+ attr_accessor :session_register
21
+
22
+ def initialize
23
+ self.session_register = {}
24
+ end
25
+
26
+ # Create a ProxyConnection according to provided configuration Hash.
27
+ # +config+ is a hash as described by ActiveRecord::Base#establish_connection
28
+ def create_session(config)
29
+ session = ProxyConnection.new config
30
+ self.session_register[session] = session
31
+ session
32
+ end
33
+
34
+ # Destroys the given session from the session register
35
+ def destroy_session(session)
36
+ session.destroy
37
+ session_register.delete session
38
+ end
39
+
40
+ # Returns 'pong'. Used to verify that a working proxy is running.
41
+ def ping
42
+ 'pong'
43
+ end
44
+
45
+ # Terminates this proxy
46
+ def terminate!
47
+ # AL: The only way I could find to kill the main thread from a sub thread
48
+ Thread.main.raise SystemExit
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,75 @@
1
+ module RR
2
+
3
+ # Scans two tables for differences.
4
+ # Doesn't have any reporting functionality by itself.
5
+ # Instead DirectTableScan#run yields all the differences for the caller to do with as it pleases.
6
+ # Usage:
7
+ # 1. Create a new DirectTableScan object and hand it all necessary information
8
+ # 2. Call DirectTableScan#run to do the actual comparison
9
+ # 3. The block handed to DirectTableScan#run receives all differences
10
+ class DirectTableScan < TableScan
11
+ include TableScanHelper
12
+
13
+ # The TypeCastingCursor for the left table
14
+ attr_accessor :left_caster
15
+
16
+ # The TypeCastingCursor for the right table
17
+ attr_accessor :right_caster
18
+
19
+ # Creates a new DirectTableScan instance
20
+ # * session: a Session object representing the current database session
21
+ # * left_table: name of the table in the left database
22
+ # * right_table: name of the table in the right database. If not given, same like left_table
23
+ def initialize(session, left_table, right_table = nil)
24
+ super
25
+ end
26
+
27
+ # Runs the table scan.
28
+ # Calls the block for every found difference.
29
+ # Differences are yielded with 2 parameters
30
+ # * type: describes the difference, either :left (row only in left table), :right (row only in right table) or :conflict
31
+ # * row: For :left or :right cases a hash describing the row; for :conflict an array of left and right row.
32
+ # A row is a hash of column_name => value pairs.
33
+ def run(&blck)
34
+ left_cursor = right_cursor = nil
35
+ left_cursor = session.left.select_cursor(
36
+ :table => left_table,
37
+ :row_buffer_size => scan_options[:row_buffer_size],
38
+ :type_cast => true
39
+ )
40
+ right_cursor = session.right.select_cursor(
41
+ :table => right_table,
42
+ :row_buffer_size => scan_options[:row_buffer_size],
43
+ :type_cast => true
44
+ )
45
+ left_row = right_row = nil
46
+ update_progress 0 # ensures progress bar is printed even if there are no records
47
+ while left_row or right_row or left_cursor.next? or right_cursor.next?
48
+ # if there is no current left row, _try_ to load the next one
49
+ left_row ||= left_cursor.next_row if left_cursor.next?
50
+ # if there is no current right row, _try_ to load the next one
51
+ right_row ||= right_cursor.next_row if right_cursor.next?
52
+ rank = rank_rows left_row, right_row
53
+ case rank
54
+ when -1
55
+ yield :left, left_row
56
+ left_row = nil
57
+ update_progress 1
58
+ when 1
59
+ yield :right, right_row
60
+ right_row = nil
61
+ update_progress 1
62
+ when 0
63
+ update_progress 2
64
+ if not left_row == right_row
65
+ yield :conflict, [left_row, right_row]
66
+ end
67
+ left_row = right_row = nil
68
+ end
69
+ # check for corresponding right rows
70
+ end
71
+ ensure
72
+ [left_cursor, right_cursor].each {|cursor| cursor.clear if cursor}
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,105 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'optparse'
4
+
5
+ module RR
6
+ # This class implements the functionality of the 'generate' command.
7
+ class GenerateRunner
8
+
9
+ CONFIG_TEMPLATE = <<EOF
10
+ RR::Initializer::run do |config|
11
+ config.left = {
12
+ :adapter => 'postgresql', # or 'mysql'
13
+ :database => 'SCOTT',
14
+ :username => 'scott',
15
+ :password => 'tiger',
16
+ :host => '172.16.1.1'
17
+ }
18
+
19
+ config.right = {
20
+ :adapter => 'postgresql',
21
+ :database => 'SCOTT',
22
+ :username => 'scott',
23
+ :password => 'tiger',
24
+ :host => '172.16.1.2'
25
+ }
26
+
27
+ config.include_tables 'dept'
28
+ config.include_tables /^e/ # regexp matching all tables starting with e
29
+ # config.include_tables /./ # regexp matching all tables in the database
30
+ end
31
+ EOF
32
+
33
+ CommandRunner.register 'generate' => {
34
+ :command => self,
35
+ :description => 'Generates a configuration file template'
36
+ }
37
+
38
+ # Provided options. Possible values:
39
+ # * +:config_file+: path to config file
40
+ attr_accessor :options
41
+
42
+ # Parses the given command line parameter array.
43
+ # Returns the status (as per UNIX conventions: 1 if parameters were invalid,
44
+ # 0 otherwise)
45
+ def process_options(args)
46
+ status = 0
47
+ self.options = {}
48
+
49
+ parser = OptionParser.new do |opts|
50
+ opts.banner = <<EOS
51
+ Usage: #{$0} generate [file_name]
52
+
53
+ Generates a configuration file template under name [file_name].
54
+ EOS
55
+ opts.separator ""
56
+ opts.separator " Specific options:"
57
+
58
+ opts.on_tail("--help", "Show this message") do
59
+ $stderr.puts opts
60
+ self.options = nil
61
+ end
62
+ end
63
+
64
+ begin
65
+ unprocessed_args = parser.parse!(args)
66
+ if options # this will be +nil+ if the --help option is specified
67
+ raise("Please specify the name of the configuration file") if unprocessed_args.empty?
68
+ options[:file_name] = unprocessed_args[0]
69
+ end
70
+ rescue Exception => e
71
+ $stderr.puts "Command line parsing failed: #{e}"
72
+ $stderr.puts parser.help
73
+ self.options = nil
74
+ status = 1
75
+ end
76
+
77
+ return status
78
+ end
79
+
80
+ # Generates a configuration file template.
81
+ def execute
82
+ if File.exists?(options[:file_name])
83
+ raise("Cowardly refuse to overwrite existing file '#{options[:file_name]}'")
84
+ end
85
+ File.open(options[:file_name], 'w') do |f|
86
+ f.write CONFIG_TEMPLATE
87
+ end
88
+ end
89
+
90
+ # Entry points for executing a processing run.
91
+ # args: the array of command line options that were provided by the user.
92
+ def self.run(args)
93
+ runner = new
94
+
95
+ status = runner.process_options(args)
96
+ if runner.options
97
+ runner.execute
98
+ end
99
+ status
100
+ end
101
+
102
+ end
103
+ end
104
+
105
+
@@ -0,0 +1,39 @@
1
+ module RR
2
+
3
+ # The settings of the current deployment are passed to Rubyrep through the
4
+ # Initializer::run method.
5
+ # This method yields a Configuration object for overwriting of the default
6
+ # settings.
7
+ # Accordingly a configuration file should look something like this:
8
+ #
9
+ # Rubyrep::Initializer.run do |config|
10
+ # config.left = ...
11
+ # end
12
+ class Initializer
13
+
14
+ # Sets a new Configuration object
15
+ # Current configuration values are lost and replaced with the default
16
+ # settings.
17
+ def self.reset
18
+ @@configuration = Configuration.new
19
+ end
20
+ reset
21
+
22
+ # Returns the current Configuration object
23
+ def self.configuration
24
+ @@configuration
25
+ end
26
+
27
+ # Allows direct overwriting of the Configuration
28
+ def self.configuration=(configuration)
29
+ @@configuration = configuration
30
+ end
31
+
32
+ # Yields the current Configuration object to enable overwriting of
33
+ # configuration values.
34
+ # Refer to the Initializer class documentation for a usage example.
35
+ def self.run
36
+ yield configuration
37
+ end
38
+ end
39
+ end