matthewtodd-taps 0.2.19
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/LICENSE +20 -0
- data/README.rdoc +41 -0
- data/Rakefile +61 -0
- data/VERSION.yml +4 -0
- data/bin/schema +42 -0
- data/bin/schema.cmd +6 -0
- data/bin/taps +5 -0
- data/lib/taps/adapter_hacks.rb +21 -0
- data/lib/taps/adapter_hacks/invalid_binary_limit.rb +13 -0
- data/lib/taps/adapter_hacks/invalid_text_limit.rb +13 -0
- data/lib/taps/adapter_hacks/mysql_invalid_primary_key.rb +17 -0
- data/lib/taps/adapter_hacks/non_rails_schema_dump.rb +15 -0
- data/lib/taps/cli.rb +62 -0
- data/lib/taps/client_session.rb +306 -0
- data/lib/taps/config.rb +33 -0
- data/lib/taps/db_session.rb +62 -0
- data/lib/taps/progress_bar.rb +236 -0
- data/lib/taps/schema.rb +94 -0
- data/lib/taps/server.rb +138 -0
- data/lib/taps/utils.rb +115 -0
- data/spec/base.rb +21 -0
- data/spec/client_session_spec.rb +88 -0
- data/spec/schema_spec.rb +45 -0
- data/spec/server_spec.rb +33 -0
- data/spec/utils_spec.rb +61 -0
- metadata +151 -0
data/lib/taps/utils.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'stringio'
|
3
|
+
require 'time'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Taps
|
7
|
+
module Utils
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def checksum(data)
|
11
|
+
Zlib.crc32(data)
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_data?(data, crc32)
|
15
|
+
Zlib.crc32(data) == crc32.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def gzip(data)
|
19
|
+
io = StringIO.new
|
20
|
+
gz = Zlib::GzipWriter.new(io)
|
21
|
+
gz.write data
|
22
|
+
gz.close
|
23
|
+
io.string
|
24
|
+
end
|
25
|
+
|
26
|
+
def gunzip(gzip_data)
|
27
|
+
io = StringIO.new(gzip_data)
|
28
|
+
gz = Zlib::GzipReader.new(io)
|
29
|
+
data = gz.read
|
30
|
+
gz.close
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_data(data, string_columns)
|
35
|
+
return {} if data.size == 0
|
36
|
+
header = data[0].keys
|
37
|
+
only_data = data.collect do |row|
|
38
|
+
row = blobs_to_string(row, string_columns)
|
39
|
+
header.collect { |h| row[h] }
|
40
|
+
end
|
41
|
+
{ :header => header, :data => only_data }
|
42
|
+
end
|
43
|
+
|
44
|
+
# mysql text and blobs fields are handled the same way internally
|
45
|
+
# this is not true for other databases so we must check if the field is
|
46
|
+
# actually text and manually convert it back to a string
|
47
|
+
def incorrect_blobs(db, table)
|
48
|
+
return [] unless db.class.to_s == "Sequel::MySQL::Database"
|
49
|
+
|
50
|
+
columns = []
|
51
|
+
db.schema(table).each do |data|
|
52
|
+
column, cdata = data
|
53
|
+
columns << column if cdata[:db_type] =~ /text/
|
54
|
+
end
|
55
|
+
columns
|
56
|
+
end
|
57
|
+
|
58
|
+
def blobs_to_string(row, columns)
|
59
|
+
return row if columns.size == 0
|
60
|
+
columns.each do |c|
|
61
|
+
row[c] = row[c].to_s if row[c].kind_of?(Sequel::SQL::Blob)
|
62
|
+
end
|
63
|
+
row
|
64
|
+
end
|
65
|
+
|
66
|
+
def calculate_chunksize(old_chunksize)
|
67
|
+
chunksize = old_chunksize
|
68
|
+
|
69
|
+
retries = 0
|
70
|
+
begin
|
71
|
+
t1 = Time.now
|
72
|
+
yield chunksize
|
73
|
+
rescue Errno::EPIPE
|
74
|
+
retries += 1
|
75
|
+
raise if retries > 1
|
76
|
+
# we got disconnected, the chunksize could be too large
|
77
|
+
# so we're resetting to a very small value
|
78
|
+
chunksize = 100
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
|
82
|
+
t2 = Time.now
|
83
|
+
|
84
|
+
diff = t2 - t1
|
85
|
+
new_chunksize = if diff > 3.0
|
86
|
+
(chunksize / 3).ceil
|
87
|
+
elsif diff > 1.1
|
88
|
+
chunksize - 100
|
89
|
+
elsif diff < 0.8
|
90
|
+
chunksize * 2
|
91
|
+
else
|
92
|
+
chunksize + 100
|
93
|
+
end
|
94
|
+
new_chunksize = 100 if new_chunksize < 100
|
95
|
+
new_chunksize
|
96
|
+
end
|
97
|
+
|
98
|
+
def primary_key(db, table)
|
99
|
+
if db.respond_to?(:primary_key)
|
100
|
+
db.primary_key(table)
|
101
|
+
else
|
102
|
+
db.schema(table).select { |c| c[1][:primary_key] }.map { |c| c.first }.shift
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def order_by(db, table)
|
107
|
+
pkey = primary_key(db, table)
|
108
|
+
if pkey
|
109
|
+
[pkey.to_sym]
|
110
|
+
else
|
111
|
+
db[table].columns
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/spec/base.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bacon'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
class Bacon::Context
|
6
|
+
include Mocha::Standalone
|
7
|
+
|
8
|
+
alias_method :old_it, :it
|
9
|
+
def it(description)
|
10
|
+
old_it(description) do
|
11
|
+
mocha_setup
|
12
|
+
yield
|
13
|
+
mocha_verify
|
14
|
+
mocha_teardown
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require File.dirname(__FILE__) + '/../lib/taps/config'
|
20
|
+
Taps::Config.taps_database_url = 'sqlite://test.db'
|
21
|
+
Sequel.connect(Taps::Config.taps_database_url)
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/taps/client_session'
|
3
|
+
|
4
|
+
describe Taps::ClientSession do
|
5
|
+
before do
|
6
|
+
@client = Taps::ClientSession.new('sqlite://my.db', 'http://example.com:3000', 1000)
|
7
|
+
@client.stubs(:session_resource).returns(mock('session resource'))
|
8
|
+
end
|
9
|
+
|
10
|
+
it "starts a session and yields the session object to the block" do
|
11
|
+
Taps::ClientSession.start('x', 'y', 1000) do |session|
|
12
|
+
session.database_url.should == 'x'
|
13
|
+
session.remote_url.should == 'y'
|
14
|
+
session.default_chunksize.should == 1000
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "opens the local db connection via sequel and the database url" do
|
19
|
+
Sequel.expects(:connect).with('sqlite://my.db').returns(:con)
|
20
|
+
@client.db.should == :con
|
21
|
+
end
|
22
|
+
|
23
|
+
it "creates a restclient resource to the remote server" do
|
24
|
+
@client.server.url.should == 'http://example.com:3000'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "verifies the db version, receive the schema, data, indexes, then reset the sequences" do
|
28
|
+
@client.expects(:verify_server)
|
29
|
+
@client.expects(:cmd_receive_schema)
|
30
|
+
@client.expects(:cmd_receive_data)
|
31
|
+
@client.expects(:cmd_receive_indexes)
|
32
|
+
@client.expects(:cmd_reset_sequences)
|
33
|
+
@client.cmd_receive.should.be.nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "checks the version of the server by seeing if it has access" do
|
37
|
+
@client.stubs(:server).returns(mock('server'))
|
38
|
+
@request = mock('request')
|
39
|
+
@client.server.expects(:[]).with('/').returns(@request)
|
40
|
+
@request.expects(:get).with({:taps_version => Taps.compatible_version})
|
41
|
+
|
42
|
+
lambda { @client.verify_server }.should.not.raise
|
43
|
+
end
|
44
|
+
|
45
|
+
it "receives data from a remote taps server" do
|
46
|
+
@client.stubs(:puts)
|
47
|
+
@progressbar = mock('progressbar')
|
48
|
+
ProgressBar.stubs(:new).with('mytable', 2).returns(@progressbar)
|
49
|
+
@progressbar.stubs(:inc)
|
50
|
+
@progressbar.stubs(:finish)
|
51
|
+
@mytable = mock('mytable')
|
52
|
+
@client.expects(:fetch_remote_tables_info).returns([ { :mytable => 2 }, 2 ])
|
53
|
+
@client.stubs(:db).returns(mock('db'))
|
54
|
+
@client.db.stubs(:[]).with(:mytable).returns(@mytable)
|
55
|
+
@client.expects(:fetch_table_rows).with(:mytable, 1000, 0).returns([ 1000, { :header => [:x, :y], :data => [[1, 2], [3, 4]] } ])
|
56
|
+
@client.expects(:fetch_table_rows).with(:mytable, 1000, 2).returns([ 1000, { }])
|
57
|
+
@mytable.expects(:import).with([:x, :y], [[1, 2], [3, 4]])
|
58
|
+
|
59
|
+
lambda { @client.cmd_receive_data }.should.not.raise
|
60
|
+
end
|
61
|
+
|
62
|
+
it "fetches remote tables info from taps server" do
|
63
|
+
@marshal_data = Marshal.dump({ :mytable => 2 })
|
64
|
+
@client.session_resource.stubs(:[]).with('tables').returns(mock('tables'))
|
65
|
+
@client.session_resource['tables'].stubs(:get).with(:taps_version => Taps.compatible_version).returns(@marshal_data)
|
66
|
+
@client.fetch_remote_tables_info.should == [ { :mytable => 2 }, 2 ]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "fetches table rows given a chunksize and offset from taps server" do
|
70
|
+
@data = { :header => [ :x, :y ], :data => [ [1, 2], [3, 4] ] }
|
71
|
+
@gzip_data = Taps::Utils.gzip(Marshal.dump(@data))
|
72
|
+
Taps::Utils.stubs(:calculate_chunksize).with(1000).yields(1000).returns(1000)
|
73
|
+
|
74
|
+
@response = mock('response')
|
75
|
+
@client.session_resource.stubs(:[]).with('tables/mytable/1000?offset=0').returns(mock('table resource'))
|
76
|
+
@client.session_resource['tables/mytable/1000?offset=0'].expects(:get).with(:taps_version => Taps.compatible_version).returns(@response)
|
77
|
+
@response.stubs(:to_s).returns(@gzip_data)
|
78
|
+
@response.stubs(:headers).returns({ :taps_checksum => Taps::Utils.checksum(@gzip_data) })
|
79
|
+
@client.fetch_table_rows('mytable', 1000, 0).should == [ 1000, { :header => [:x, :y], :data => [[1, 2], [3, 4]] } ]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "hides the password in urls" do
|
83
|
+
@client.safe_url("postgres://postgres:password@localhost/mydb").should == "postgres://postgres:[hidden]@localhost/mydb"
|
84
|
+
@client.safe_url("postgres://postgres@localhost/mydb").should == "postgres://postgres@localhost/mydb"
|
85
|
+
@client.safe_url("http://x:y@localhost:5000").should == "http://x:[hidden]@localhost:5000"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
data/spec/schema_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/taps/schema'
|
3
|
+
|
4
|
+
describe Taps::Schema do
|
5
|
+
before do
|
6
|
+
Taps::AdapterHacks.stubs(:load)
|
7
|
+
@connection = mock("AR connection")
|
8
|
+
ActiveRecord::Base.stubs(:connection).returns(@connection)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses a database url and returns a config hash for activerecord" do
|
12
|
+
Taps::Schema.create_config("postgres://myuser:mypass@localhost/mydb").should == {
|
13
|
+
'adapter' => 'postgresql',
|
14
|
+
'database' => 'mydb',
|
15
|
+
'username' => 'myuser',
|
16
|
+
'password' => 'mypass',
|
17
|
+
'host' => 'localhost'
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
it "translates sqlite in the database url to sqlite3" do
|
22
|
+
Taps::Schema.create_config("sqlite://mydb")['adapter'].should == 'sqlite3'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "translates sqlite database path" do
|
26
|
+
Taps::Schema.create_config("sqlite://pathtodb/mydb")['database'].should == 'pathtodb/mydb'
|
27
|
+
Taps::Schema.create_config("sqlite:///pathtodb/mydb")['database'].should == '/pathtodb/mydb'
|
28
|
+
Taps::Schema.create_config("sqlite:///pathtodb/mydb?encoding=utf8")['database'].should == '/pathtodb/mydb'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "connects activerecord to the database" do
|
32
|
+
Taps::Schema.expects(:create_config).with("postgres://myuser:mypass@localhost/mydb").returns("db config")
|
33
|
+
ActiveRecord::Base.expects(:establish_connection).with("db config").returns(true)
|
34
|
+
Taps::Schema.connection("postgres://myuser:mypass@localhost/mydb").should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "resets the db tables' primary keys" do
|
38
|
+
Taps::Schema.stubs(:connection)
|
39
|
+
ActiveRecord::Base.connection.expects(:respond_to?).with(:reset_pk_sequence!).returns(true)
|
40
|
+
ActiveRecord::Base.connection.stubs(:tables).returns(['table1'])
|
41
|
+
ActiveRecord::Base.connection.expects(:reset_pk_sequence!).with('table1')
|
42
|
+
should.not.raise { Taps::Schema.reset_db_sequences("postgres://myuser:mypass@localhost/mydb") }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'sinatra/test/bacon'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/../lib/taps/server'
|
6
|
+
|
7
|
+
require 'pp'
|
8
|
+
|
9
|
+
describe Taps::Server do
|
10
|
+
before do
|
11
|
+
Taps::Config.login = 'taps'
|
12
|
+
Taps::Config.password = 'tpass'
|
13
|
+
|
14
|
+
@app = Taps::Server
|
15
|
+
@auth_header = "Basic " + ["taps:tpass"].pack("m*")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "asks for http basic authentication" do
|
19
|
+
get '/'
|
20
|
+
status.should == 401
|
21
|
+
end
|
22
|
+
|
23
|
+
it "verifies the client taps version" do
|
24
|
+
get('/', { }, { 'HTTP_AUTHORIZATION' => @auth_header, 'HTTP_TAPS_VERSION' => Taps.compatible_version })
|
25
|
+
status.should == 200
|
26
|
+
end
|
27
|
+
|
28
|
+
it "yells loudly if the client taps version doesn't match" do
|
29
|
+
get('/', { }, { 'HTTP_AUTHORIZATION' => @auth_header, 'HTTP_TAPS_VERSION' => '0.0.1' })
|
30
|
+
status.should == 417
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
data/spec/utils_spec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/taps/utils'
|
3
|
+
|
4
|
+
describe Taps::Utils do
|
5
|
+
it "gunzips a string" do
|
6
|
+
@hello_world = "\037\213\b\000R\261\207I\000\003\313H\315\311\311W(\317/\312I\001\000\205\021J\r\v\000\000\000"
|
7
|
+
Taps::Utils.gunzip(@hello_world).should == "hello world"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "gzips and gunzips a string and returns the same string" do
|
11
|
+
Taps::Utils.gunzip(Taps::Utils.gzip("hello world")).should == "hello world"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "generates a checksum using crc32" do
|
15
|
+
Taps::Utils.checksum("hello world").should == Zlib.crc32("hello world")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "formats a data hash into one hash that contains an array of headers and an array of array of data" do
|
19
|
+
first_row = { :x => 1, :y => 1 }
|
20
|
+
first_row.stubs(:keys).returns([:x, :y])
|
21
|
+
Taps::Utils.format_data([ first_row, { :x => 2, :y => 2 } ], []).should == { :header => [ :x, :y ], :data => [ [1, 1], [2, 2] ] }
|
22
|
+
end
|
23
|
+
|
24
|
+
it "scales chunksize down slowly when the time delta of the block is just over a second" do
|
25
|
+
Time.stubs(:now).returns(10.0).returns(11.5)
|
26
|
+
Taps::Utils.calculate_chunksize(1000) { }.should == 900
|
27
|
+
end
|
28
|
+
|
29
|
+
it "scales chunksize down fast when the time delta of the block is over 3 seconds" do
|
30
|
+
Time.stubs(:now).returns(10.0).returns(15.0)
|
31
|
+
Taps::Utils.calculate_chunksize(3000) { }.should == 1000
|
32
|
+
end
|
33
|
+
|
34
|
+
it "scales up chunksize fast when the time delta of the block is under 0.8 seconds" do
|
35
|
+
Time.stubs(:now).returns(10.0).returns(10.7)
|
36
|
+
Taps::Utils.calculate_chunksize(1000) { }.should == 2000
|
37
|
+
end
|
38
|
+
|
39
|
+
it "scales up chunksize slow when the time delta of the block is between 0.8 and 1.1 seconds" do
|
40
|
+
Time.stubs(:now).returns(10.0).returns(10.8)
|
41
|
+
Taps::Utils.calculate_chunksize(1000) { }.should == 1100
|
42
|
+
|
43
|
+
Time.stubs(:now).returns(10.0).returns(11.1)
|
44
|
+
Taps::Utils.calculate_chunksize(1000) { }.should == 1100
|
45
|
+
end
|
46
|
+
|
47
|
+
it "will reset the chunksize to a small value if we got a broken pipe exception" do
|
48
|
+
Taps::Utils.calculate_chunksize(1000) { |c| raise Errno::EPIPE if c == 1000; c.should == 100 }.should == 200
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns a list of columns that are text fields if the database is mysql" do
|
52
|
+
@db = mock("db")
|
53
|
+
@db.class.stubs(:to_s).returns("Sequel::MySQL::Database")
|
54
|
+
@db.stubs(:schema).with(:mytable).returns([
|
55
|
+
[:id, { :db_type => "int" }],
|
56
|
+
[:mytext, { :db_type => "text" }]
|
57
|
+
])
|
58
|
+
Taps::Utils.incorrect_blobs(@db, :mytable).should == [:mytext]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: matthewtodd-taps
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.19
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ricardo Chimal, Jr.
|
8
|
+
- Adam Wiggins
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-11-08 00:00:00 +03:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: sinatra
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - "="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.9.2
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activerecord
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.3.4
|
35
|
+
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: thor
|
38
|
+
type: :runtime
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - "="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 0.9.9
|
45
|
+
version:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rest-client
|
48
|
+
type: :runtime
|
49
|
+
version_requirement:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.1
|
55
|
+
- - <
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.1.0
|
58
|
+
version:
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: sequel
|
61
|
+
type: :runtime
|
62
|
+
version_requirement:
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 3.0.0
|
68
|
+
- - <
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 3.1.0
|
71
|
+
version:
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: sqlite3-ruby
|
74
|
+
type: :runtime
|
75
|
+
version_requirement:
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ~>
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 1.2.0
|
81
|
+
version:
|
82
|
+
description: A simple database agnostic import/export app to transfer data to/from a remote database.
|
83
|
+
email: ricardo@heroku.com
|
84
|
+
executables:
|
85
|
+
- taps
|
86
|
+
- schema
|
87
|
+
extensions: []
|
88
|
+
|
89
|
+
extra_rdoc_files:
|
90
|
+
- LICENSE
|
91
|
+
- README.rdoc
|
92
|
+
files:
|
93
|
+
- LICENSE
|
94
|
+
- README.rdoc
|
95
|
+
- Rakefile
|
96
|
+
- VERSION.yml
|
97
|
+
- bin/schema
|
98
|
+
- bin/schema.cmd
|
99
|
+
- bin/taps
|
100
|
+
- lib/taps/adapter_hacks.rb
|
101
|
+
- lib/taps/adapter_hacks/invalid_binary_limit.rb
|
102
|
+
- lib/taps/adapter_hacks/invalid_text_limit.rb
|
103
|
+
- lib/taps/adapter_hacks/mysql_invalid_primary_key.rb
|
104
|
+
- lib/taps/adapter_hacks/non_rails_schema_dump.rb
|
105
|
+
- lib/taps/cli.rb
|
106
|
+
- lib/taps/client_session.rb
|
107
|
+
- lib/taps/config.rb
|
108
|
+
- lib/taps/db_session.rb
|
109
|
+
- lib/taps/progress_bar.rb
|
110
|
+
- lib/taps/schema.rb
|
111
|
+
- lib/taps/server.rb
|
112
|
+
- lib/taps/utils.rb
|
113
|
+
- spec/base.rb
|
114
|
+
- spec/client_session_spec.rb
|
115
|
+
- spec/schema_spec.rb
|
116
|
+
- spec/server_spec.rb
|
117
|
+
- spec/utils_spec.rb
|
118
|
+
has_rdoc: true
|
119
|
+
homepage: http://github.com/matthewtodd/taps
|
120
|
+
licenses: []
|
121
|
+
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options:
|
124
|
+
- --charset=UTF-8
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: "0"
|
132
|
+
version:
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: "0"
|
138
|
+
version:
|
139
|
+
requirements: []
|
140
|
+
|
141
|
+
rubyforge_project: taps
|
142
|
+
rubygems_version: 1.3.5
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: simple database import/export app
|
146
|
+
test_files:
|
147
|
+
- spec/base.rb
|
148
|
+
- spec/client_session_spec.rb
|
149
|
+
- spec/schema_spec.rb
|
150
|
+
- spec/server_spec.rb
|
151
|
+
- spec/utils_spec.rb
|