monkey_butler 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +122 -0
- data/Guardfile +8 -0
- data/LICENSE +202 -0
- data/README.md +221 -0
- data/Rakefile +16 -0
- data/bin/mb +6 -0
- data/lib/monkey_butler.rb +1 -0
- data/lib/monkey_butler/actions.rb +38 -0
- data/lib/monkey_butler/cli.rb +354 -0
- data/lib/monkey_butler/databases/abstract_database.rb +49 -0
- data/lib/monkey_butler/databases/cassandra_database.rb +69 -0
- data/lib/monkey_butler/databases/sqlite_database.rb +105 -0
- data/lib/monkey_butler/migrations.rb +52 -0
- data/lib/monkey_butler/project.rb +90 -0
- data/lib/monkey_butler/targets/base.rb +85 -0
- data/lib/monkey_butler/targets/cassandra/cassandra_target.rb +72 -0
- data/lib/monkey_butler/targets/cassandra/create_schema_migrations.cql.erb +16 -0
- data/lib/monkey_butler/targets/cassandra/migration.cql.erb +0 -0
- data/lib/monkey_butler/targets/cocoapods/cocoapods_target.rb +43 -0
- data/lib/monkey_butler/targets/cocoapods/podspec.erb +12 -0
- data/lib/monkey_butler/targets/maven/maven_target.rb +60 -0
- data/lib/monkey_butler/targets/maven/project/.gitignore +6 -0
- data/lib/monkey_butler/targets/maven/project/MonkeyButler.iml +19 -0
- data/lib/monkey_butler/targets/maven/project/build.gradle +54 -0
- data/lib/monkey_butler/targets/sqlite/create_monkey_butler_tables.sql.erb +15 -0
- data/lib/monkey_butler/targets/sqlite/migration.sql.erb +11 -0
- data/lib/monkey_butler/targets/sqlite/sqlite_target.rb +91 -0
- data/lib/monkey_butler/templates/Gemfile.erb +4 -0
- data/lib/monkey_butler/templates/gitignore.erb +1 -0
- data/lib/monkey_butler/util.rb +71 -0
- data/lib/monkey_butler/version.rb +3 -0
- data/logo.jpg +0 -0
- data/monkey_butler.gemspec +33 -0
- data/spec/cli_spec.rb +700 -0
- data/spec/databases/cassandra_database_spec.rb +241 -0
- data/spec/databases/sqlite_database_spec.rb +181 -0
- data/spec/migrations_spec.rb +4 -0
- data/spec/project_spec.rb +128 -0
- data/spec/sandbox/cassandra/.gitignore +2 -0
- data/spec/sandbox/cassandra/.monkey_butler.yml +7 -0
- data/spec/sandbox/cassandra/migrations/20140523123443021_create_sandbox.cql.sql +14 -0
- data/spec/sandbox/cassandra/sandbox.cql +0 -0
- data/spec/sandbox/sqlite/.gitignore +2 -0
- data/spec/sandbox/sqlite/.monkey_butler.yml +7 -0
- data/spec/sandbox/sqlite/migrations/20140523123443021_create_sandbox.sql +14 -0
- data/spec/sandbox/sqlite/sandbox.sql +0 -0
- data/spec/spec_helper.rb +103 -0
- data/spec/targets/cassandra_target_spec.rb +191 -0
- data/spec/targets/cocoapods_target_spec.rb +197 -0
- data/spec/targets/maven_target_spec.rb +156 -0
- data/spec/targets/sqlite_target_spec.rb +103 -0
- data/spec/util_spec.rb +13 -0
- metadata +260 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
/* Create Monkey Butler Tables */
|
2
|
+
|
3
|
+
-- Maintains list of applied migrations
|
4
|
+
USE KEYSPACE <%= keyspace -%>;
|
5
|
+
CREATE TABLE schema_migrations(
|
6
|
+
version INTEGER UNIQUE NOT NULL
|
7
|
+
);
|
8
|
+
|
9
|
+
/* Create application tables */
|
10
|
+
|
11
|
+
/*
|
12
|
+
CREATE TABLE table_name (
|
13
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
14
|
+
...
|
15
|
+
);
|
16
|
+
*/
|
File without changes
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'monkey_butler/targets/base'
|
2
|
+
|
3
|
+
module MonkeyButler
|
4
|
+
module Targets
|
5
|
+
class CocoapodsTarget < MonkeyButler::Targets::Base
|
6
|
+
def init
|
7
|
+
unless project.config['cocoapods.repo']
|
8
|
+
project.config['cocoapods.repo'] = ask("What is the name of your Cocoapods specs repo? ")
|
9
|
+
end
|
10
|
+
if options['bundler']
|
11
|
+
append_to_file 'Gemfile', "gem 'cocoapods'\n"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate
|
16
|
+
invoke :validate
|
17
|
+
template('podspec.erb', podspec_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
fail Error, "Invalid configuration: cocoapods.repo is not configured." unless cocoapods_repo
|
22
|
+
end
|
23
|
+
|
24
|
+
def push
|
25
|
+
invoke :validate
|
26
|
+
run "pod repo push #{options['quiet'] && '--silent '}--allow-warnings #{cocoapods_repo} #{podspec_name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def cocoapods_repo
|
31
|
+
project.config['cocoapods.repo']
|
32
|
+
end
|
33
|
+
|
34
|
+
def podspec_name
|
35
|
+
"#{project.name}.podspec"
|
36
|
+
end
|
37
|
+
|
38
|
+
def pod_version
|
39
|
+
unique_tag_for_version(migrations.latest_version)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Pod::Spec.new do |s|
|
2
|
+
s.name = "<%= project.name %>"
|
3
|
+
s.version = "<%= pod_version %>"
|
4
|
+
s.summary = "Packages the database schema and migrations for <%= project.name -%>"
|
5
|
+
s.homepage = "<%= project.config['homepage'] || "http://github.com/layerhq" %>"
|
6
|
+
s.author = { "<%= project.git_user_name -%>" => "<%= project.git_user_email -%>" }
|
7
|
+
s.source = { :git => "<%= project.git_url -%>", :tag => "<%= pod_version -%>" }
|
8
|
+
s.license = 'Commercial'
|
9
|
+
s.requires_arc = true
|
10
|
+
|
11
|
+
s.resource_bundles = { '<%= project.name -%>' => ['migrations/*', '<%= project.schema_path -%>'] }
|
12
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'monkey_butler/targets/base'
|
2
|
+
|
3
|
+
module MonkeyButler
|
4
|
+
module Targets
|
5
|
+
class MavenTarget < MonkeyButler::Targets::Base
|
6
|
+
def init
|
7
|
+
unless project.config['maven.url']
|
8
|
+
project.config['maven.url'] = ask("What is the URL of your Java Maven repo? ")
|
9
|
+
end
|
10
|
+
unless project.config['maven.username']
|
11
|
+
project.config['maven.username'] = ask("What is the username for your Java Maven repo? ")
|
12
|
+
end
|
13
|
+
unless project.config['maven.password']
|
14
|
+
project.config['maven.password'] = ask("What is the password for your Java Maven repo? ")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate
|
19
|
+
invoke :validate
|
20
|
+
remove_file "project"
|
21
|
+
empty_directory "project"
|
22
|
+
empty_directory "project/src"
|
23
|
+
empty_directory "project/src/main"
|
24
|
+
empty_directory "project/src/main/resources"
|
25
|
+
empty_directory "project/src/main/resources/schema"
|
26
|
+
|
27
|
+
copy_file "project/build.gradle", "project/build.gradle"
|
28
|
+
FileUtils.cp_r project.schema_path, "project/src/main/resources/schema/schema.sql"
|
29
|
+
FileUtils.cp_r project.migrations_path, "project/src/main/resources"
|
30
|
+
|
31
|
+
run "cd project && gradle#{options['quiet'] && ' -q '} -Pversion=#{migrations.latest_version} clean jar"
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate
|
35
|
+
fail Error, "Invalid configuration: maven.repo is not configured." unless maven_url
|
36
|
+
fail Error, "Invalid configuration: maven.username is not configured." unless maven_username
|
37
|
+
fail Error, "Invalid configuration: maven.password is not configured." unless maven_password
|
38
|
+
end
|
39
|
+
|
40
|
+
def push
|
41
|
+
invoke :validate
|
42
|
+
run "cd project && gradle#{options['quiet'] && ' -q'} -Pversion=#{migrations.latest_version} -Purl=#{maven_url} -Pusername=#{maven_username} -Ppassword=#{maven_password} publish"
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def maven_url
|
48
|
+
project.config['maven.url']
|
49
|
+
end
|
50
|
+
|
51
|
+
def maven_username
|
52
|
+
project.config['maven.username']
|
53
|
+
end
|
54
|
+
|
55
|
+
def maven_password
|
56
|
+
project.config['maven.password']
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="0.1.1" type="JAVA_MODULE" version="4">
|
3
|
+
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
4
|
+
<output url="file://$MODULE_DIR$/build/classes/main" />
|
5
|
+
<output-test url="file://$MODULE_DIR$/build/classes/test" />
|
6
|
+
<exclude-output />
|
7
|
+
<content url="file://$MODULE_DIR$">
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
11
|
+
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
12
|
+
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
13
|
+
<excludeFolder url="file://$MODULE_DIR$/build" />
|
14
|
+
</content>
|
15
|
+
<orderEntry type="inheritedJdk" />
|
16
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
17
|
+
</component>
|
18
|
+
</module>
|
19
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
apply plugin: 'java'
|
2
|
+
apply plugin: 'maven'
|
3
|
+
apply plugin: 'maven-publish'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Specify:
|
7
|
+
* -Pversion=[VERSION]
|
8
|
+
* -Purl=[MAVEN URL TO PUBLISH TO] e.g. "http://nexus.dev.layer.com:8081/nexus/content/repositories/releases" or "${System.env.HOME}/.m2/repository"
|
9
|
+
* -Pusername=[USERNAME FOR MAVEN PUBLISH]
|
10
|
+
* -Ppassword=[PASSWORD FOR MAVEN PUBLISH]
|
11
|
+
*/
|
12
|
+
|
13
|
+
ext {
|
14
|
+
mavenUrl = rootProject.hasProperty("url") ? rootProject.property("url") : "${System.env.HOME}/.m2/repository"
|
15
|
+
mavenUsername = rootProject.hasProperty("username") ? rootProject.property("username") : null;
|
16
|
+
mavenPassword = rootProject.hasProperty("password") ? rootProject.property("password") : null;
|
17
|
+
}
|
18
|
+
|
19
|
+
compileJava {
|
20
|
+
sourceCompatibility = 1.6
|
21
|
+
targetCompatibility = 1.6
|
22
|
+
}
|
23
|
+
|
24
|
+
repositories {
|
25
|
+
mavenCentral()
|
26
|
+
}
|
27
|
+
|
28
|
+
/*************************************************
|
29
|
+
* Uploading
|
30
|
+
************************************************/
|
31
|
+
task Jar(type: Jar) {
|
32
|
+
jar.archiveName = "monkeybutler-" + version + ".jar"
|
33
|
+
}
|
34
|
+
|
35
|
+
publishing {
|
36
|
+
publications {
|
37
|
+
mavenJava(MavenPublication) {
|
38
|
+
groupId "com.layer"
|
39
|
+
artifactId "monkeybutler"
|
40
|
+
from components.java
|
41
|
+
}
|
42
|
+
}
|
43
|
+
repositories {
|
44
|
+
maven {
|
45
|
+
credentials {
|
46
|
+
username = mavenUsername;
|
47
|
+
password = mavenPassword;
|
48
|
+
}
|
49
|
+
url = mavenUrl;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/* Create Monkey Butler Tables */
|
2
|
+
|
3
|
+
-- Maintains list of applied migrations
|
4
|
+
CREATE TABLE schema_migrations(
|
5
|
+
version INTEGER UNIQUE NOT NULL
|
6
|
+
);
|
7
|
+
|
8
|
+
/* Create application tables */
|
9
|
+
|
10
|
+
/*
|
11
|
+
CREATE TABLE table_name (
|
12
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
13
|
+
...
|
14
|
+
);
|
15
|
+
*/
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'monkey_butler/databases/sqlite_database'
|
2
|
+
|
3
|
+
module MonkeyButler
|
4
|
+
module Targets
|
5
|
+
class SqliteTarget < Base
|
6
|
+
# TODO: Need a way to do this for self elegantly...
|
7
|
+
def self.register_with_cli(cli)
|
8
|
+
cli.method_option :database, type: :string, aliases: '-d', desc: "Set target DATABASE", for: :dump
|
9
|
+
cli.method_option :database, type: :string, aliases: '-d', desc: "Set target DATABASE", for: :load
|
10
|
+
end
|
11
|
+
|
12
|
+
def init
|
13
|
+
migration_name = MonkeyButler::Util.migration_named('create_' + options['name'])
|
14
|
+
template('create_monkey_butler_tables.sql.erb', "migrations/#{migration_name}.sql")
|
15
|
+
git_add "migrations/#{migration_name}.sql"
|
16
|
+
create_file(database_path)
|
17
|
+
append_to_file '.gitignore', database_path
|
18
|
+
end
|
19
|
+
|
20
|
+
no_commands do
|
21
|
+
# NOTE: Used instead of database.url to avoid creation of the database in pretend mode
|
22
|
+
def database_path
|
23
|
+
database_url.path || database_url.opaque
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def new(name)
|
28
|
+
migration_ext = project.database_class.migration_ext
|
29
|
+
migration_name = MonkeyButler::Util.migration_named(name) + migration_ext
|
30
|
+
template('migration.sql.erb', "migrations/#{migration_name}")
|
31
|
+
git_add "migrations/#{migration_name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def dump
|
35
|
+
database_url = (options[:database] && URI(options[:database])) || project.database_url
|
36
|
+
database_path = database_url.path || database_url.opaque
|
37
|
+
fail Error, "Cannot dump database: no file at path '#{database_path}'." unless File.exists?(database_path)
|
38
|
+
|
39
|
+
@database = MonkeyButler::Databases::SqliteDatabase.new(database_url)
|
40
|
+
fail Error, "Cannot dump database: the database at path '#{database_path}' does not have a `schema_migrations` table." unless database.migrations_table?
|
41
|
+
say "Dumping schema from database '#{database_path}'"
|
42
|
+
|
43
|
+
File.truncate(project.schema_path, 0)
|
44
|
+
|
45
|
+
types = { table: 'tables', index: 'indexes', trigger: 'triggers', view: 'views'}
|
46
|
+
types.each do |type, name|
|
47
|
+
say "Dumping #{name}..."
|
48
|
+
with_padding do
|
49
|
+
database.dump_to_schema(type, project.schema_path) do |name|
|
50
|
+
say "wrote #{type}: #{name}", :green
|
51
|
+
end
|
52
|
+
end
|
53
|
+
say
|
54
|
+
end
|
55
|
+
|
56
|
+
say "Dumping rows from 'schema_migrations'..."
|
57
|
+
with_padding do
|
58
|
+
File.open(project.schema_path, 'a') do |f|
|
59
|
+
database.all_versions.each do |version|
|
60
|
+
f.puts "INSERT INTO schema_migrations(version) VALUES (#{version});\n\n"
|
61
|
+
say "wrote version: #{version}", :green
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
say
|
66
|
+
|
67
|
+
say "Dump complete. Schema written to #{project.schema_path}."
|
68
|
+
end
|
69
|
+
|
70
|
+
def load
|
71
|
+
project = MonkeyButler::Project.load
|
72
|
+
unless File.size?(project.schema_path)
|
73
|
+
raise Error, "Cannot load database: empty schema found at #{project.schema_path}. Maybe you need to `mb migrate`?"
|
74
|
+
end
|
75
|
+
|
76
|
+
drop
|
77
|
+
command = "sqlite3 #{database.path} < #{project.schema_path}"
|
78
|
+
say_status :executing, command
|
79
|
+
stdout_str, stderr_str, status = Open3.capture3(command)
|
80
|
+
fail Error, "Failed loading schema: #{stderr_str}" unless stderr_str.empty?
|
81
|
+
|
82
|
+
say "Loaded schema at version #{database.current_version}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def drop
|
86
|
+
say_status :truncate, database.path, :yellow
|
87
|
+
database.drop
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module MonkeyButler
|
2
|
+
class Util
|
3
|
+
class << self
|
4
|
+
def migration_timestamp
|
5
|
+
Time.now.strftime('%Y%m%d%H%M%S%3N').to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
def migration_named(name, timestamp = migration_timestamp)
|
9
|
+
migration_name = [timestamp, Thor::Util.snake_case(name)].join('_')
|
10
|
+
end
|
11
|
+
|
12
|
+
def strip_leading_whitespace(string)
|
13
|
+
string.gsub(/^\s+/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def migration_version_from_path(path)
|
17
|
+
path.match(/(\d{17})_/)[1].to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def migration_versions_from_paths(paths)
|
21
|
+
paths.map { |path| migration_version_from_path(path) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def migrations_by_version(paths)
|
25
|
+
paths.inject({}) do |hash, path|
|
26
|
+
version = migration_version_from_path(path)
|
27
|
+
hash[version] = path
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def camelize(term, uppercase_first_letter = true)
|
33
|
+
string = term.to_s
|
34
|
+
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
|
35
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
36
|
+
string.gsub!('/', '::')
|
37
|
+
string
|
38
|
+
end
|
39
|
+
|
40
|
+
def database_named(name)
|
41
|
+
raise ArgumentError, "Database name cannot be nil." if name.nil?
|
42
|
+
require "monkey_butler/databases/#{name}_database"
|
43
|
+
klass_name = "MonkeyButler::Databases::#{Util.camelize(name)}Database"
|
44
|
+
Object.const_get(klass_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_classes_named(*names)
|
48
|
+
raise ArgumentError, "Database name cannot be nil." if names.nil?
|
49
|
+
names.flatten.map do |name|
|
50
|
+
require "monkey_butler/targets/#{name}/#{name}_target"
|
51
|
+
klass_name = "MonkeyButler::Targets::#{Util.camelize(name)}Target"
|
52
|
+
Object.const_get(klass_name).tap do |klass|
|
53
|
+
yield klass if block_given?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def unique_tag_for_version(version)
|
59
|
+
revision = nil
|
60
|
+
tag = nil
|
61
|
+
begin
|
62
|
+
tag = [version, revision].compact.join('.')
|
63
|
+
existing_tag = `git tag -l #{tag}`
|
64
|
+
break if existing_tag == ""
|
65
|
+
revision = revision.to_i + 1
|
66
|
+
end while true
|
67
|
+
tag
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/logo.jpg
ADDED
Binary file
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'monkey_butler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'monkey_butler'
|
8
|
+
s.version = '1.2.2'
|
9
|
+
s.date = '2014-06-15'
|
10
|
+
s.summary = "Monkey Butler is a schema management system for SQLite"
|
11
|
+
s.description = "A simple hello world gem"
|
12
|
+
s.authors = ["Blake Watters"]
|
13
|
+
s.email = 'blake@layer.com'
|
14
|
+
s.homepage = 'http://github.com/layerhq/monkey_butler'
|
15
|
+
s.license = 'Apache 2'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split($/)
|
18
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'thor', '~> 0.19.1'
|
23
|
+
s.add_dependency 'sqlite3', '~> 1.3.9'
|
24
|
+
s.add_dependency 'cql-rb', '~> 2.0.0'
|
25
|
+
|
26
|
+
s.add_development_dependency "bundler", "~> 1.6"
|
27
|
+
s.add_development_dependency "rake"
|
28
|
+
s.add_development_dependency "rspec", "~> 2.14.1"
|
29
|
+
s.add_development_dependency "guard-rspec", "~> 4.2.9"
|
30
|
+
s.add_development_dependency "simplecov", "~> 0.8.2"
|
31
|
+
s.add_development_dependency "byebug", "~> 3.1.2"
|
32
|
+
s.add_development_dependency "cocoapods", "~> 0.33.1"
|
33
|
+
end
|