koke-mydiff 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.1 / 2008-05-13
2
+
3
+ * First public ALPHA release
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/mydiff
6
+ lib/mydiff/change.rb
7
+ lib/mydiff/cli.rb
8
+ lib/mydiff.rb
9
+ test/helper.rb
10
+ test/test_mydiff.rb
data/README.txt ADDED
@@ -0,0 +1,51 @@
1
+ = MyDiff MySQL diff library
2
+
3
+ http://github.com/koke/mydiff
4
+
5
+ == DESCRIPTION:
6
+
7
+ MySQL diff library
8
+
9
+ *WARNING* Although this code is public, it's not complete yet.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * FIXME
14
+
15
+ == SYNOPSIS:
16
+
17
+ mydiff -o mydiff -n mydiff_new
18
+
19
+ == REQUIREMENTS:
20
+
21
+ * mysql
22
+ * highline
23
+
24
+ == INSTALL:
25
+
26
+ * sudo gem install mydiff
27
+
28
+ == LICENSE:
29
+
30
+ (The MIT License)
31
+
32
+ Copyright (c) 2008 FIX
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining
35
+ a copy of this software and associated documentation files (the
36
+ 'Software'), to deal in the Software without restriction, including
37
+ without limitation the rights to use, copy, modify, merge, publish,
38
+ distribute, sublicense, and/or sell copies of the Software, and to
39
+ permit persons to whom the Software is furnished to do so, subject to
40
+ the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be
43
+ included in all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
46
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
49
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
50
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
51
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+ require "rake/testtask"
5
+ require 'hoe'
6
+ require './lib/mydiff.rb'
7
+
8
+ Hoe.new('mydiff', MyDiff::VERSION) do |p|
9
+ p.author = 'Jorge Bernal'
10
+ p.email = 'jbernal@warp.es'
11
+ p.summary = 'MySQL diff library'
12
+ p.description = p.paragraphs_of('README.txt', 2..2).join("\n\n")
13
+ p.url = p.paragraphs_of('README.txt', 1).first
14
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
15
+ p.extra_deps << ['mysql','>= 2.7']
16
+ p.extra_deps << ['highline', '>= 1.4.0']
17
+ p.remote_rdoc_dir = ''
18
+ end
19
+
20
+ desc "Open an irb session preloaded with this library"
21
+ task :console do
22
+ sh "irb -rubygems -r ./lib/mydiff.rb"
23
+ end
24
+
25
+ desc "Run coverage tests"
26
+ task :coverage do
27
+ system("rm -fr coverage")
28
+ system("rcov test/test_*.rb")
29
+ system("open coverage/index.html")
30
+ end
data/bin/mydiff ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if File.directory?("lib")
4
+ $: << "lib"
5
+ end
6
+
7
+ require "rubygems"
8
+ require "mydiff"
9
+ require "optparse"
10
+
11
+
12
+ $DEBUG = false
13
+
14
+ config = {
15
+ :host => "localhost",
16
+ :user => "root",
17
+ :password => nil
18
+ }
19
+
20
+ opts = OptionParser.new do |opts|
21
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] -o OLDDB -n NEWDB"
22
+
23
+ opts.on("-d", "--debug", "Show debug messages") do |v|
24
+ $DEBUG = v
25
+ end
26
+
27
+ opts.on("-o", "--olddb OLDDB", "Database to apply changes") do |o|
28
+ config[:olddb] = o
29
+ end
30
+
31
+ opts.on("-n", "--newdb NEWDB", "Database to read changes") do |n|
32
+ config[:newdb] = n
33
+ end
34
+
35
+ opts.on("-h", "--host name", "MySQL host to connect (default #{config[:host]})") do |o|
36
+ config[:host] = o
37
+ end
38
+
39
+ opts.on("-u", "--user name", "MySQL username (default #{config[:user]})") do |o|
40
+ config[:user] = o
41
+ end
42
+
43
+ opts.on("-p", "--password passwd", "MySQL password") do |o|
44
+ config[:password] = o
45
+ end
46
+
47
+ end
48
+
49
+ opts.parse!
50
+
51
+ unless config[:newdb] and config[:olddb]
52
+ puts opts.help
53
+ exit 1
54
+ end
55
+
56
+ md = MyDiff.new(config)
57
+ md.cli.main_menu
58
+
59
+ # popt = config[:password].nil? ? "" : "-p#{config[:password]}"
60
+ # system "mysql -h #{config[:host]} -u #{config[:user]} #{popt} #{md.newdb} < new.sql"
61
+ # system "mysql -h #{config[:host]} -u #{config[:user]} #{popt} #{md.olddb} < old.sql"
data/lib/mydiff.rb ADDED
@@ -0,0 +1,230 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require "mysql"
4
+ require "mydiff/cli"
5
+ require "mydiff/change"
6
+
7
+ # MyDiff helps you to apply changes from one MySQL database to another
8
+ #
9
+ # It has some helper methods to
10
+ #
11
+ # Example
12
+ #
13
+ # md = MyDiff.new(:host => "localhost", :user => "root", :newdb => "mydiff_new", :olddb => "mydiff_old") # => <MyDiff>
14
+ # md.newdb #=> "mydiff_new"
15
+ # md.olddb #=> "mydiff_old"
16
+ # md.new_tables #=> ["new_table1", "new_table2"]
17
+ # md.dropped_tables #=> ["old_table"]
18
+ class MyDiff
19
+ VERSION = '0.0.1'
20
+
21
+ # Name of the database with changes
22
+ attr_accessor :newdb
23
+ # Name of the current database. Changes will be applied here
24
+ attr_accessor :olddb
25
+ # Command Line Interface. See MyDiff::CLI
26
+ attr_accessor :cli
27
+ attr_accessor :my #:nodoc:
28
+
29
+ # Creates a new MyDiff instance
30
+ #
31
+ # Config options
32
+ # - <tt>:host</tt> - MySQL host
33
+ # - <tt>:user</tt> - MySQL user
34
+ # - <tt>:password</tt> - MySQL password
35
+ # - <tt>:newdb</tt> - Name of the database with the changes to apply
36
+ # - <tt>:olddb</tt> - Name of the database to apply the changes
37
+ #
38
+ # Returns MyDiff
39
+ def initialize(config)
40
+ @my = Mysql::new(config[:host], config[:user], config[:password])
41
+ @newdb = config[:newdb]
42
+ @olddb = config[:olddb]
43
+ @cli = CLI.new(self)
44
+ @fields = {}
45
+ end
46
+
47
+ # Recreates the new database
48
+ #
49
+ # *WARNING*: This method drops and recreates the +newdb+ database, you may lose data!
50
+ def prepare!
51
+ begin
52
+ @my.query("DROP DATABASE #{@newdb}")
53
+ rescue
54
+ end
55
+
56
+ @my.query("CREATE DATABASE #{@newdb}")
57
+ end
58
+
59
+ # Returns an array with table names for the database given in +db+
60
+ def list_tables(db)
61
+ @my.select_db(db)
62
+ @my.list_tables
63
+ end
64
+
65
+ # Returns an array with table names present on +newdb+ but not on +olddb+
66
+ def new_tables
67
+ ntables = list_tables(@newdb)
68
+ otables = list_tables(@olddb)
69
+
70
+ ntables.select {|t| not otables.include?(t) }
71
+ end
72
+
73
+ # Returns an array with table names present on +olddb+ but not on +newdb+
74
+ def dropped_tables
75
+ ntables = list_tables(@newdb)
76
+ otables = list_tables(@olddb)
77
+
78
+ otables.select {|t| not ntables.include?(t) }
79
+ end
80
+
81
+ # Returns an array with table names present on +newdb+ and +olddb+ which
82
+ # are different in content
83
+ def changed_tables
84
+ ntables = list_tables(@newdb)
85
+ otables = list_tables(@olddb)
86
+
87
+ ntables.select {|t| otables.include?(t) and table_changed?(t) }
88
+ end
89
+
90
+ # Returns an array with rows present in +newdb+ but not in +olddb+, using the +table+ given
91
+ def new_rows(table)
92
+ fields = fields_from(table)
93
+ pkey, fields = extract_pkey_from(fields)
94
+ my.select_db(@newdb)
95
+
96
+ query = "SELECT "
97
+ query << pkey.collect do |f|
98
+ "n.#{f["Field"]} #{f["Field"]}"
99
+ end.join(",")
100
+ query << ","
101
+ query << fields.collect do |f|
102
+ "n.#{f["Field"]} n_#{f["Field"]}"
103
+ end.join(",")
104
+
105
+ query << " FROM #{@newdb}.#{table} AS n LEFT JOIN #{@olddb}.#{table} AS o ON "
106
+ query << pkey.collect do |f|
107
+ "n.#{f["Field"]} = o.#{f["Field"]}"
108
+ end.join(" AND ")
109
+ query << " WHERE "
110
+ query << pkey.collect do |f|
111
+ "o.#{f["Field"]} IS NULL"
112
+ end.join(" AND ")
113
+
114
+ result = my.query(query)
115
+ new_rows = []
116
+ while row = result.fetch_hash
117
+ new_rows << row
118
+ end
119
+ new_rows
120
+ end
121
+
122
+ def deleted_rows(table)
123
+ fields = fields_from(table)
124
+ pkey, fields = extract_pkey_from(fields)
125
+ my.select_db(@olddb)
126
+
127
+ query = "SELECT "
128
+ query << pkey.collect do |f|
129
+ "o.#{f["Field"]} #{f["Field"]}"
130
+ end.join(",")
131
+ query << ","
132
+ query << fields.collect do |f|
133
+ "o.#{f["Field"]} o_#{f["Field"]}"
134
+ end.join(",")
135
+
136
+ query << " FROM #{@olddb}.#{table} AS o LEFT JOIN #{@newdb}.#{table} AS n ON "
137
+ query << pkey.collect do |f|
138
+ "n.#{f["Field"]} = o.#{f["Field"]}"
139
+ end.join(" AND ")
140
+ query << " WHERE "
141
+ query << pkey.collect do |f|
142
+ "n.#{f["Field"]} IS NULL"
143
+ end.join(" AND ")
144
+
145
+ result = my.query(query)
146
+ deleted_rows = []
147
+ while row = result.fetch_hash
148
+ deleted_rows << row
149
+ end
150
+ deleted_rows
151
+ end
152
+
153
+ def changed_rows(table)
154
+ fields = fields_from(table)
155
+ pkey, fields = extract_pkey_from(fields)
156
+ my.select_db(@olddb)
157
+
158
+ query = "SELECT "
159
+ query << pkey.collect do |f|
160
+ "o.#{f["Field"]} #{f["Field"]}"
161
+ end.join(",")
162
+ query << ","
163
+ query << fields.collect do |f|
164
+ "o.#{f["Field"]} o_#{f["Field"]}, n.#{f["Field"]} n_#{f["Field"]}"
165
+ end.join(",")
166
+
167
+ query << " FROM #{@olddb}.#{table} AS o INNER JOIN #{@newdb}.#{table} AS n ON "
168
+ query << pkey.collect do |f|
169
+ "n.#{f["Field"]} = o.#{f["Field"]}"
170
+ end.join(" AND ")
171
+
172
+ result = my.query(query)
173
+ changed_rows = []
174
+ while row = result.fetch_hash
175
+ changed_rows << row
176
+ end
177
+ changed_rows.select do |row|
178
+ fields.inject(true) do |s,f|
179
+ s and row["o_#{f["Field"]}"] == row["n_#{f["Field"]}"]
180
+ end
181
+ end
182
+ end
183
+
184
+ def fields_from(table)
185
+ return @fields[table] if @fields[table]
186
+ @my.select_db(@newdb)
187
+ res = @my.query("DESCRIBE #{table}")
188
+ fields = []
189
+ while (field = res.fetch_hash)
190
+ fields << field
191
+ end
192
+
193
+ @fields[table] ||= fields
194
+ end
195
+
196
+ def count_rows(db, table)
197
+ @my.select_db(db)
198
+ res = @my.query("SELECT COUNT(*) FROM #{table}")
199
+ res.fetch_row[0]
200
+ end
201
+
202
+ def extract_pkey_from(fields)
203
+ fields.partition {|f| f["Key"] == "PRI" }
204
+ end
205
+
206
+ def pkey_of(table)
207
+ fields_from(table).select {|f| f["Key"] == "PRI"}.map {|f| f["Field"]}
208
+ end
209
+
210
+ def data_fields_of(table)
211
+ fields_from(table).select {|f| f["Key"] != "PRI"}.map {|f| f["Field"]}
212
+ end
213
+
214
+ def checksum_table(db, table)
215
+ @my.select_db(db)
216
+ @my.query("CHECKSUM TABLE #{table}").fetch_row[1]
217
+ end
218
+
219
+ def table_changed?(table)
220
+ checksum_table(@olddb, table) != checksum_table(@newdb, table)
221
+ end
222
+
223
+ def select_new
224
+ @my.select_db(@newdb)
225
+ end
226
+
227
+ def select_old
228
+ @my.select_db(@olddb)
229
+ end
230
+ end
@@ -0,0 +1,105 @@
1
+ require "yaml"
2
+
3
+ class MyDiff
4
+ # Represents one change to apply to the database
5
+ class Change
6
+ # Can be one of <tt>:new</tt>, <tt>:drop</tt>, <tt>:overwrite</tt> or <tt>:patch</tt>
7
+ attr_accessor :type
8
+ attr_accessor :chunks
9
+
10
+ # Create a new change for a specific table
11
+ #
12
+ # +parent+:: the MyDiff parent class
13
+ # +type+:: one of <tt>:new</tt>, <tt>:drop</tt>, <tt>:overwrite</tt> or <tt>:patch</tt>
14
+ # +table+:: the table to change
15
+ def initialize(parent, type, table)
16
+ @md = parent
17
+ @type = type
18
+ @table = table
19
+ @chunks = {}
20
+ end
21
+
22
+ # Adds a chunk to apply in the change.
23
+ #
24
+ # +type+:: can be either <tt>:new</tt>, <tt>:delete</tt> or <tt>:change</tt>
25
+ # +pkey+:: should be a hash containing primary key fields
26
+ # +fields+:: should be a hash containint the fields not belonging to primary key (only for +type+ = <tt>:new</tt> or <tt>:change</tt>)
27
+ def add_chunk(type, pkey, fields = {})
28
+ raise ArgumentError, "Chunks can be added only if type is :patch" unless @type.eql?(:patch)
29
+ @chunks[pkey.to_yaml] = { :type => type, :pkey => pkey, :fields => fields}
30
+ end
31
+
32
+ def delete_chunk(pkey)
33
+ @chunks.delete(pkey.to_yaml)
34
+ end
35
+
36
+ def get_chunk(pkey)
37
+ @chunks[pkey.to_yaml]
38
+ end
39
+
40
+ def has_chunk?(pkey)
41
+ @chunks.has_key?(pkey.to_yaml)
42
+ end
43
+
44
+ def apply!
45
+ if @type.eql?(:new)
46
+ puts "** Creating #{@table}"
47
+ @md.select_old
48
+ @md.my.query("CREATE TABLE #{@table} LIKE #{@md.newdb}.#{@table}")
49
+ @md.my.query("INSERT INTO #{@table} SELECT * FROM #{@md.newdb}.#{@table}")
50
+ elsif @type.eql?(:drop)
51
+ puts "** Dropping table #{@table}"
52
+ @md.select_old
53
+ @md.my.query("DROP TABLE #{@table}")
54
+ elsif @type.eql?(:overwrite)
55
+ raise NotImplementedError, "Type :overwrite is not yet in use"
56
+ elsif @type.eql?(:patch)
57
+ printf "** Updating table #{@table}"
58
+ @chunks.each_pair do |pkey, data|
59
+ printf "."
60
+ @md.select_old
61
+ if data[:type].eql?(:new)
62
+ field_list = []
63
+ data_list = []
64
+ data[:pkey].each_pair do |k,v|
65
+ field_list << k
66
+ data_list << "'" + @md.my.escape_string(v) + "'"
67
+ end
68
+ data[:fields].each_pair do |k,v|
69
+ field_list << k
70
+ data_list << "'" + @md.my.escape_string(v) + "'"
71
+ end
72
+
73
+ puts("INSERT INTO #{@table} (#{field_list.join(',')}) VALUES(#{data_list.join(',')})") if $DEBUG
74
+ @md.my.query("INSERT INTO #{@table} (#{field_list.join(',')}) VALUES(#{data_list.join(',')})")
75
+ elsif data[:type].eql?(:change)
76
+ changes_list = []
77
+ pkey_list = []
78
+ data[:pkey].each_pair do |k,v|
79
+ pkey_list << "#{k} = '" + @md.my.escape_string(v) + "'"
80
+ end
81
+ data[:fields].each_pair do |k,v|
82
+ changes_list << "#{k} = '" + @md.my.escape_string(v) + "'"
83
+ end
84
+
85
+ puts("UPDATE #{@table} SET #{changes_list.join(',')} WHERE #{pkey_list.join(' AND ')}") if $DEBUG
86
+ @md.my.query("UPDATE #{@table} SET #{changes_list.join(',')} WHERE #{pkey_list.join(' AND ')}")
87
+ elsif data[:type].eql?(:delete)
88
+ pkey_list = []
89
+ data[:pkey].each_pair do |k,v|
90
+ pkey_list << "#{k} = '" + @md.my.escape_string(v) + "'"
91
+ end
92
+ puts("DELETE FROM #{@table} WHERE #{pkey_list.join(' AND ')}") if $DEBUG
93
+ @md.my.query("DELETE FROM #{@table} WHERE #{pkey_list.join(' AND ')}")
94
+ else
95
+ raise NotImplementedError, "Invalid chunk type: #{data[:type]}"
96
+ end
97
+ end
98
+
99
+ puts
100
+ else
101
+ raise NotImplementedError, "Invalid type #{@type.to_s}"
102
+ end
103
+ end
104
+ end
105
+ end
data/lib/mydiff/cli.rb ADDED
@@ -0,0 +1,204 @@
1
+ require "highline"
2
+
3
+ class MyDiff
4
+ class CLI < HighLine
5
+ def initialize(parent)
6
+ @md = parent
7
+ @tables = []
8
+ @md.new_tables.each {|t| @tables << {:status => :new, :table => t}}
9
+ @md.dropped_tables.each {|t| @tables << {:status => :drop, :table => t}}
10
+ @md.changed_tables.each {|t| @tables << {:status => :change, :table => t}}
11
+
12
+ @tables.sort! {|t1,t2| t1[:table] <=> t2[:table]}
13
+ @changes = {}
14
+ super()
15
+ end
16
+
17
+ def main_menu
18
+ continue = true
19
+ begin
20
+ while continue
21
+ choose do |menu|
22
+ menu.prompt = "What now?"
23
+
24
+ menu.choice("Status") { status }
25
+ menu.choice("Accept") { accept }
26
+ menu.choice("Reject") { reject }
27
+ menu.choice("Patch") { patch }
28
+ menu.choice("Apply") { apply! }
29
+ menu.choice("Quit") { continue = false }
30
+ end
31
+ end
32
+ rescue Interrupt
33
+ end
34
+ end
35
+
36
+ def status(type = :all)
37
+ type = [type] unless type.is_a?(Array)
38
+ @tables.each_with_index do |table, count|
39
+ if type.include?(:all) or type.include?(table[:status])
40
+ render_row(count, table[:table], table[:status])
41
+ end
42
+ end
43
+ end
44
+
45
+ def accept
46
+ submenu do |table|
47
+ change = @changes[table[:table]]
48
+ if change #and change.type.eql?(:patch)
49
+ @changes.delete(table[:table])
50
+ end
51
+ @changes[table[:table]] = Change.new(@md, :add, table[:table])
52
+ end
53
+ end
54
+
55
+ def reject
56
+ submenu do |table|
57
+ change = @changes[table[:table]]
58
+ if change #and change.type.eql?(:patch)
59
+ @changes.delete(table[:table])
60
+ end
61
+ end
62
+ end
63
+
64
+ def patch
65
+ submenu([:change, :new]) do |table|
66
+ continue = true
67
+ while continue
68
+ rows = []
69
+ @md.deleted_rows(table[:table]).each {|r| rows << {:status => :delete, :row => r}}
70
+ @md.changed_rows(table[:table]).each {|r| rows << {:status => :change, :row => r}}
71
+ @md.new_rows(table[:table]).each {|r| rows << {:status => :new, :row => r}}
72
+ rows.each_with_index do |row, count|
73
+ status = row[:status]
74
+ row = row[:row]
75
+ pkey = {}
76
+ @md.pkey_of(table[:table]).each do |f|
77
+ pkey[f] = row[f]
78
+ end
79
+ ndata = @md.data_fields_of(table[:table]).map do |f|
80
+ row["n_#{f}"]
81
+ end
82
+ odata = @md.data_fields_of(table[:table]).map do |f|
83
+ row["o_#{f}"]
84
+ end
85
+
86
+ begin
87
+ if @changes[table[:table]].has_chunk?(pkey)
88
+ status = "*"
89
+ else
90
+ status = " "
91
+ end
92
+ rescue
93
+ status = " "
94
+ end
95
+ say ""
96
+ say "%2d. [%s] (%s) %s" % [count, status, pkey.values.join("||"), odata.join("||")]
97
+ puts " [%s] (%s) %s" % [status, pkey.values.join("||"), ndata.join("||")]
98
+
99
+ end
100
+
101
+ opt = ask "Which one?"
102
+ if opt.empty? or opt.downcase.eql?("q")
103
+ continue = false
104
+ else
105
+ if opt =~ /(\d+)-(\d+)/
106
+ opts = ($1..$2).to_a
107
+ elsif opt =~ /(\d+)/
108
+ opts = [$1]
109
+ end
110
+ opts.each do |opt|
111
+ row = rows[opt.to_i]
112
+ if row.nil? or not opt =~ /[0-9+]/
113
+ say "Wrong answer!"
114
+ next
115
+ end
116
+ change = @changes[table[:table]]
117
+ if (change and not change.type.eql?(:patch)) or not change
118
+ @changes.delete(table[:table])
119
+ change = Change.new(@md, :patch, table[:table])
120
+ end
121
+
122
+ pkey = {}
123
+ @md.pkey_of(table[:table]).each do |f|
124
+ pkey[f] = row[:row][f]
125
+ end
126
+ if change.has_chunk?(pkey)
127
+ change.delete_chunk(pkey)
128
+ else
129
+ data = {}
130
+ @md.data_fields_of(table[:table]).each do |f|
131
+ data[f] = row[:row]["n_#{f}"]
132
+ end
133
+ change.add_chunk(row[:status], pkey, data)
134
+ end
135
+
136
+ @changes[table[:table]] = change
137
+ if change.chunks.empty?
138
+ @changes.delete(table[:table])
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def apply!
147
+ @changes.each_value do |change|
148
+ change.apply!
149
+ end
150
+ end
151
+
152
+ private
153
+ def submenu(type = :all)
154
+ continue = true
155
+ while continue
156
+ status(type)
157
+ opt = ask "Which one?"
158
+ if opt.empty? or opt.downcase.eql?("q")
159
+ continue = false
160
+ else
161
+ if opt =~ /(\d+)-(\d+)/
162
+ opts = ($1..$2).to_a
163
+ elsif opt =~ /(\d+)/
164
+ opts = [$1]
165
+ end
166
+ opts.each do |opt|
167
+ table = @tables[opt.to_i]
168
+ if table.nil? or not opt =~ /[0-9+]/
169
+ say "Wrong answer!"
170
+ next
171
+ end
172
+ yield table
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ def render_row(count, table, status)
179
+ if @changes.has_key?(table)
180
+ if @changes[table].type.eql?(:patch)
181
+ change = "/"
182
+ else
183
+ change = "*"
184
+ end
185
+ else
186
+ change = " "
187
+ end
188
+
189
+ if status.eql?(:new)
190
+ say "%2d. [%s] N %8d %s" % [count, change, "+" + @md.count_rows(@md.newdb, table), table]
191
+ elsif status.eql?(:drop)
192
+ say "%2d. [%s] D %8d %s" % [count, change, "-" + @md.count_rows(@md.olddb, table), table]
193
+ elsif status.eql?(:change)
194
+ new_rows = @md.new_rows(table).size
195
+ deleted_rows = @md.deleted_rows(table).size
196
+ changed_rows = @md.changed_rows(table).size
197
+ new_rows += changed_rows
198
+ deleted_rows += changed_rows
199
+ changed_rows = "+#{new_rows}/-#{deleted_rows}"
200
+ say "%2d. [%s] C %8s %s" % [count, change, changed_rows, table]
201
+ end
202
+ end
203
+ end
204
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), *%w[.. lib mydiff])
4
+
5
+ def absolute_project_path
6
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
7
+ end
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestMydiff < Test::Unit::TestCase
4
+ def setup
5
+
6
+ end
7
+
8
+ def test_true
9
+ assert(true)
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: koke-mydiff
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jorge Bernal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-13 00:00:00 -07:00
13
+ default_executable: mydiff
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mysql
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "2.7"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: highline
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.4.0
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: hoe
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.5.1
41
+ version:
42
+ description: "== DESCRIPTION:"
43
+ email: jbernal@warp.es
44
+ executables:
45
+ - mydiff
46
+ extensions: []
47
+
48
+ extra_rdoc_files:
49
+ - History.txt
50
+ - Manifest.txt
51
+ - README.txt
52
+ files:
53
+ - History.txt
54
+ - Manifest.txt
55
+ - README.txt
56
+ - Rakefile
57
+ - bin/mydiff
58
+ - lib/mydiff/change.rb
59
+ - lib/mydiff/cli.rb
60
+ - lib/mydiff.rb
61
+ - test/helper.rb
62
+ - test/test_mydiff.rb
63
+ has_rdoc: true
64
+ homepage: http://github.com/koke/mydiff
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --main
68
+ - README.txt
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project: mydiff
86
+ rubygems_version: 1.0.1
87
+ signing_key:
88
+ specification_version: 2
89
+ summary: MySQL diff library
90
+ test_files:
91
+ - test/test_mydiff.rb