monkey_butler 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +2 -0
  3. data/.gitignore +3 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +122 -0
  8. data/Guardfile +8 -0
  9. data/LICENSE +202 -0
  10. data/README.md +221 -0
  11. data/Rakefile +16 -0
  12. data/bin/mb +6 -0
  13. data/lib/monkey_butler.rb +1 -0
  14. data/lib/monkey_butler/actions.rb +38 -0
  15. data/lib/monkey_butler/cli.rb +354 -0
  16. data/lib/monkey_butler/databases/abstract_database.rb +49 -0
  17. data/lib/monkey_butler/databases/cassandra_database.rb +69 -0
  18. data/lib/monkey_butler/databases/sqlite_database.rb +105 -0
  19. data/lib/monkey_butler/migrations.rb +52 -0
  20. data/lib/monkey_butler/project.rb +90 -0
  21. data/lib/monkey_butler/targets/base.rb +85 -0
  22. data/lib/monkey_butler/targets/cassandra/cassandra_target.rb +72 -0
  23. data/lib/monkey_butler/targets/cassandra/create_schema_migrations.cql.erb +16 -0
  24. data/lib/monkey_butler/targets/cassandra/migration.cql.erb +0 -0
  25. data/lib/monkey_butler/targets/cocoapods/cocoapods_target.rb +43 -0
  26. data/lib/monkey_butler/targets/cocoapods/podspec.erb +12 -0
  27. data/lib/monkey_butler/targets/maven/maven_target.rb +60 -0
  28. data/lib/monkey_butler/targets/maven/project/.gitignore +6 -0
  29. data/lib/monkey_butler/targets/maven/project/MonkeyButler.iml +19 -0
  30. data/lib/monkey_butler/targets/maven/project/build.gradle +54 -0
  31. data/lib/monkey_butler/targets/sqlite/create_monkey_butler_tables.sql.erb +15 -0
  32. data/lib/monkey_butler/targets/sqlite/migration.sql.erb +11 -0
  33. data/lib/monkey_butler/targets/sqlite/sqlite_target.rb +91 -0
  34. data/lib/monkey_butler/templates/Gemfile.erb +4 -0
  35. data/lib/monkey_butler/templates/gitignore.erb +1 -0
  36. data/lib/monkey_butler/util.rb +71 -0
  37. data/lib/monkey_butler/version.rb +3 -0
  38. data/logo.jpg +0 -0
  39. data/monkey_butler.gemspec +33 -0
  40. data/spec/cli_spec.rb +700 -0
  41. data/spec/databases/cassandra_database_spec.rb +241 -0
  42. data/spec/databases/sqlite_database_spec.rb +181 -0
  43. data/spec/migrations_spec.rb +4 -0
  44. data/spec/project_spec.rb +128 -0
  45. data/spec/sandbox/cassandra/.gitignore +2 -0
  46. data/spec/sandbox/cassandra/.monkey_butler.yml +7 -0
  47. data/spec/sandbox/cassandra/migrations/20140523123443021_create_sandbox.cql.sql +14 -0
  48. data/spec/sandbox/cassandra/sandbox.cql +0 -0
  49. data/spec/sandbox/sqlite/.gitignore +2 -0
  50. data/spec/sandbox/sqlite/.monkey_butler.yml +7 -0
  51. data/spec/sandbox/sqlite/migrations/20140523123443021_create_sandbox.sql +14 -0
  52. data/spec/sandbox/sqlite/sandbox.sql +0 -0
  53. data/spec/spec_helper.rb +103 -0
  54. data/spec/targets/cassandra_target_spec.rb +191 -0
  55. data/spec/targets/cocoapods_target_spec.rb +197 -0
  56. data/spec/targets/maven_target_spec.rb +156 -0
  57. data/spec/targets/sqlite_target_spec.rb +103 -0
  58. data/spec/util_spec.rb +13 -0
  59. 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
+ */
@@ -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,6 @@
1
+ .gradle
2
+ /local.properties
3
+ /.idea/workspace.xml
4
+ .DS_Store
5
+ /build
6
+ /classes
@@ -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,11 @@
1
+ -- Create or alter tables
2
+
3
+ /*
4
+ CREATE TABLE table_name (
5
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT
6
+ );
7
+
8
+ ALTER TABLE table_name (
9
+ ...
10
+ )
11
+ */
@@ -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,4 @@
1
+ source "http://rubygems.org"
2
+ ruby '2.0.0'
3
+
4
+ gem 'monkey_butler'
@@ -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
@@ -0,0 +1,3 @@
1
+ module MonkeyButler
2
+ VERSION = "1.2.2"
3
+ 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