koke-mydiff 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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