monkey_butler 1.2.2

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.
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