prick 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.gitignore +3 -5
- data/Gemfile +4 -1
- data/TODO +3 -0
- data/doc/prick.txt +114 -0
- data/exe/prick +224 -370
- data/lib/ext/fileutils.rb +11 -0
- data/lib/ext/forward_method.rb +18 -0
- data/lib/ext/shortest_path.rb +44 -0
- data/lib/prick.rb +17 -9
- data/lib/prick/branch.rb +254 -0
- data/lib/prick/builder.rb +141 -0
- data/lib/prick/command.rb +19 -11
- data/lib/prick/constants.rb +42 -20
- data/lib/prick/database.rb +5 -3
- data/lib/prick/diff.rb +47 -0
- data/lib/prick/exceptions.rb +15 -3
- data/lib/prick/git.rb +46 -21
- data/lib/prick/migration.rb +165 -185
- data/lib/prick/program.rb +238 -0
- data/lib/prick/project.rb +266 -358
- data/lib/prick/rdbms.rb +2 -2
- data/lib/prick/schema.rb +19 -88
- data/lib/prick/share.rb +64 -0
- data/lib/prick/state.rb +137 -0
- data/lib/prick/version.rb +34 -14
- data/libexec/strip-comments +33 -0
- data/make_releases +48 -345
- data/make_schema +10 -0
- data/prick.gemspec +11 -22
- data/share/feature_migration/diff.sql +2 -0
- data/share/feature_migration/migrate.sql +2 -0
- data/share/release_migration/diff.sql +3 -0
- data/share/release_migration/features.yml +6 -0
- data/share/release_migration/migrate.sql +5 -0
- data/share/release_migration/migrate.yml +5 -0
- data/share/schema/build.yml +14 -0
- data/share/schema/schema.sql +5 -0
- data/share/schemas/build.yml +3 -0
- data/share/schemas/prick/build.yml +14 -0
- data/share/schemas/prick/data.sql +1 -2
- data/share/schemas/prick/schema.sql +0 -15
- data/share/schemas/prick/tables.sql +17 -0
- data/share/schemas/public/.keep +0 -0
- data/share/schemas/public/build.yml +14 -0
- data/share/schemas/public/schema.sql +3 -0
- data/test_assorted +192 -0
- data/test_feature +112 -0
- data/test_single_dev +83 -0
- metadata +34 -61
data/lib/prick/rdbms.rb
CHANGED
data/lib/prick/schema.rb
CHANGED
@@ -1,100 +1,31 @@
|
|
1
|
-
require "prick/
|
2
|
-
require "prick/exceptions.rb"
|
1
|
+
require "prick/state.rb"
|
3
2
|
|
4
3
|
module Prick
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
# Alternatively add some special tags:
|
12
|
-
#
|
13
|
-
# -- require prick (meaning include the prick schema)
|
14
|
-
# -- require public/user-schema (include this file)
|
15
|
-
#
|
16
|
-
class Schema
|
17
|
-
# Enclosing project
|
18
|
-
attr_reader :project
|
19
|
-
|
20
|
-
# Path to data file
|
21
|
-
def Schema.data_file() DATA_SQL_PATH end
|
22
|
-
def data_file() Schema.data_file end
|
23
|
-
|
24
|
-
# Version read from schemas/prick/data.sql
|
25
|
-
def version()
|
26
|
-
@version ||= begin
|
27
|
-
File.open(data_file, "r") { |file|
|
28
|
-
while !file.eof? && file.gets.chomp != COPY_STMT
|
29
|
-
;
|
30
|
-
end
|
31
|
-
!file.eof? or raise Fail, "No COPY statement in #{data_file}"
|
32
|
-
l = file.gets.chomp
|
33
|
-
a = l.split("\t")[1..].map { |val| val == '\N' ? nil : val }
|
34
|
-
a.size == FIELDS.size or raise Fail, "Illegal data format in #{data_file}"
|
35
|
-
custom, major, minor, patch, pre = a[0], *a[1..-2].map { |val| val && val.to_i }
|
36
|
-
v = Version.new("0.0.0", custom: (custom == '\N' ? nil : custom))
|
37
|
-
v.major = major
|
38
|
-
v.minor = minor
|
39
|
-
v.patch = patch
|
40
|
-
v.pre = (pre == "null" ? nil : pre)
|
41
|
-
v
|
42
|
-
}
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Write version number into schemas/prick/data.sql
|
47
|
-
def version=(version)
|
48
|
-
@version = Version.new(version)
|
49
|
-
File.open(data_file, "w") { |f|
|
50
|
-
f.puts "--"
|
51
|
-
f.puts "-- This file is auto-generated by prick(1). Please don't touch"
|
52
|
-
f.puts COPY_STMT
|
53
|
-
f.print \
|
54
|
-
"1\t",
|
55
|
-
FIELDS[..-2].map { |f| @version.send(f.to_sym) || '\N' }.join("\t"),
|
56
|
-
"\t#{@version}\n"
|
57
|
-
f.puts "\\."
|
58
|
-
}
|
59
|
-
Git.add(data_file)
|
60
|
-
@version
|
61
|
-
end
|
4
|
+
PRICK_SCHEMA = "prick"
|
5
|
+
SCHEMA_NAMES = %w(schema roles types tables data constraints indexes views functions comments grants)
|
6
|
+
BUILD_BASE_NAME = "build"
|
7
|
+
BUILD_SQL_FILE = BUILD_BASE_NAME + ".sql"
|
8
|
+
BUILD_YML_FILE = BUILD_BASE_NAME + ".yml"
|
62
9
|
|
63
|
-
|
64
|
-
|
65
|
-
|
10
|
+
# Note this models the SCHEMAS_DIR directory, not a database schema
|
11
|
+
class Schema
|
12
|
+
attr_reader :directory
|
13
|
+
def yml_file() SchemaBuilder.yml_file(directory) end
|
66
14
|
|
67
|
-
|
68
|
-
def
|
15
|
+
def version() SchemaVersion.new(directory).read end
|
16
|
+
def version=(version) SchemaVersion.new(directory).write(version) end
|
17
|
+
def version_file() SchemaVersion.new(directory).path end
|
69
18
|
|
70
|
-
def
|
71
|
-
|
19
|
+
def initialize(directory = SCHEMAS_DIR)
|
20
|
+
@directory = directory
|
72
21
|
end
|
73
22
|
|
74
|
-
|
75
|
-
def build(database = project.database)
|
76
|
-
files = collect("schema.sql") + collect("data.sql")
|
77
|
-
Dir.chdir(path) {
|
78
|
-
files.each { |file| Rdbms.exec_file(database.name, file, user: project.user) }
|
79
|
-
}
|
80
|
-
end
|
23
|
+
def built?(database) database.exist? && database.version == version end
|
81
24
|
|
82
|
-
#
|
83
|
-
def
|
84
|
-
|
85
|
-
if File.exist?(filename)
|
86
|
-
[filename]
|
87
|
-
else
|
88
|
-
Dir["*/#{filename}"]
|
89
|
-
end
|
90
|
-
}
|
25
|
+
# `subject` can be a subpath of schema/ (ie. 'public/tables')
|
26
|
+
def build(database, subject = nil)
|
27
|
+
SchemaBuilder.new(database, directory).build(subject)
|
91
28
|
end
|
92
|
-
|
93
|
-
private
|
94
|
-
FIELDS = %w(custom major minor patch pre feature version)
|
95
|
-
COPY_STMT = "COPY prick.versions (id, #{FIELDS.join(', ')}) FROM stdin;"
|
96
|
-
|
97
|
-
DATA_SQL_PATH = "#{Prick::PRICK_DIR}/data.sql"
|
98
29
|
end
|
99
30
|
end
|
100
31
|
|
data/lib/prick/share.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
module Prick
|
3
|
+
class Share
|
4
|
+
# Procedural object for templating and copying share/ files
|
5
|
+
class Copier
|
6
|
+
attr_reader :clobber, :templates
|
7
|
+
|
8
|
+
def initialize(clobber, templates)
|
9
|
+
@clobber = clobber
|
10
|
+
@templates = templates
|
11
|
+
end
|
12
|
+
|
13
|
+
def cp(from, to)
|
14
|
+
if File.directory?(from)
|
15
|
+
cp_dir(from, to)
|
16
|
+
elsif File.file?(from)
|
17
|
+
cp_file(from, to)
|
18
|
+
else
|
19
|
+
raise Fail, "Can't copy #{from}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def cp_file(from, to)
|
24
|
+
if clobber || !File.exist?(to)
|
25
|
+
if templates.empty?
|
26
|
+
FileUtils.copy_file(from, to)
|
27
|
+
else
|
28
|
+
File.open(to, "w") { |f|
|
29
|
+
File.readlines(from).each { |l|
|
30
|
+
templates.each { |key, value| l.gsub!(/\[<#{key}>\]/, value) }
|
31
|
+
f.puts l
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
[to]
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def cp_dir(from, to)
|
42
|
+
FileUtils.mkdir_p(to)
|
43
|
+
Dir.children(from).map { |name|
|
44
|
+
cp(File.join(from, name), File.join(to, name))
|
45
|
+
}.flatten
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.cp(pattern, dest, clobber: true, templates: {})
|
50
|
+
copier = Copier.new(clobber, templates)
|
51
|
+
matches = Dir.glob(File.join(SHARE_PATH, pattern))
|
52
|
+
if File.directory?(dest)
|
53
|
+
matches.map { |from| copier.cp(from, File.join(dest, File.basename(from))) }.flatten
|
54
|
+
elsif matches.size == 1
|
55
|
+
copier.cp(matches.first, dest)
|
56
|
+
elsif matches.size == 0
|
57
|
+
[]
|
58
|
+
else
|
59
|
+
raise Internal, "Destination is not a directory: #{destdir}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/lib/prick/state.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Prick
|
5
|
+
class PrickFile
|
6
|
+
attr_reader :path
|
7
|
+
def initialize(path) @path = path end
|
8
|
+
def exist?() File.exist?(path) end
|
9
|
+
|
10
|
+
protected
|
11
|
+
def general_read(method, branch: nil, tag: nil)
|
12
|
+
branch || tag ? Git.send(method, path, branch: branch, tag: tag) : File.open(path, "r").send(method)
|
13
|
+
end
|
14
|
+
def do_read(**opts) general_read(:read, **opts) end
|
15
|
+
def do_readlines(**opts) general_read(:readlines, **opts) end
|
16
|
+
end
|
17
|
+
|
18
|
+
class State < PrickFile
|
19
|
+
# `fields` is a Hash from field name (Symbol) to field type (class). Eg. { f: Integer }
|
20
|
+
def initialize(path, **fields)
|
21
|
+
super(path)
|
22
|
+
@fields = fields
|
23
|
+
@fields.each_key { |k| self.class.attr_accessor k }
|
24
|
+
end
|
25
|
+
|
26
|
+
def create() write end
|
27
|
+
|
28
|
+
def set(**fields)
|
29
|
+
for field, value in fields
|
30
|
+
self.send(:"#{field}=", value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def read(branch: nil, tag: nil)
|
35
|
+
if branch.nil?
|
36
|
+
hash = YAML.load(do_read(branch: branch, tag: tag))
|
37
|
+
for field, klass in @fields
|
38
|
+
value = hash[field.to_s]
|
39
|
+
value = Version.new(value) if klass == Version && !value.nil?
|
40
|
+
self.instance_variable_set("@#{field}", value)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise NotYet
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def write
|
49
|
+
hash = @fields.map { |field, klass|
|
50
|
+
value = self.send(field)
|
51
|
+
value = value.to_s if klass == Version && !value.nil?
|
52
|
+
[field.to_s, value]
|
53
|
+
}.to_h
|
54
|
+
IO.write(@path, YAML.dump(hash))
|
55
|
+
raise if @version.is_a?(Array)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class MigrationState < State
|
61
|
+
def initialize(directory, version: nil, base_version: nil)
|
62
|
+
super(File.join(directory, MIGRATION_STATE_FILE), version: Version, base_version: Version)
|
63
|
+
set(version: version, base_version: base_version)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class FeaturesState < State
|
68
|
+
def initialize(directory, features: [])
|
69
|
+
super(File.join(directory, FEATURES_STATE_FILE), features: Array)
|
70
|
+
set(features: features)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class ProjectState < State
|
75
|
+
def initialize(name: nil, user: nil)
|
76
|
+
super(PROJECT_STATE_FILE, name: String, user: String)
|
77
|
+
set(name: name, user: user)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class PrickVersion < PrickFile
|
82
|
+
def initialize() super PRICK_VERSION_FILE end
|
83
|
+
|
84
|
+
def read(branch: nil, tag: nil)
|
85
|
+
Version.new(do_readlines(branch: branch, tag: tag).first.chomp.sub(/^prick-/, ""))
|
86
|
+
end
|
87
|
+
|
88
|
+
def write(version)
|
89
|
+
File.open(path, "w") { |file| file.puts "prick-#{version}" }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class SchemaVersion < PrickFile
|
94
|
+
def initialize(schema = SCHEMAS_DIR) super(File.join(schema, "prick", "data.sql")) end
|
95
|
+
|
96
|
+
def read(**opts)
|
97
|
+
lines = do_readlines
|
98
|
+
while !lines.empty?
|
99
|
+
line = lines.shift.chomp
|
100
|
+
next if line != COPY_STMT
|
101
|
+
l = lines.shift.chomp
|
102
|
+
a = l.split("\t")[1..].map { |val| val == '\N' ? nil : val }
|
103
|
+
a.size == FIELDS.size or raise Fail, "Illegal data format in #{path}"
|
104
|
+
custom, major, minor, patch, pre = a[0], *a[1..-2].map { |val| val && val.to_i }
|
105
|
+
v = Version.new("0.0.0", custom: (custom == '\N' ? nil : custom))
|
106
|
+
v.major = major
|
107
|
+
v.minor = minor
|
108
|
+
v.patch = patch
|
109
|
+
v.pre = (pre == "null" ? nil : pre)
|
110
|
+
return v
|
111
|
+
end
|
112
|
+
raise Fail, "No COPY statement in #{path}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def write(version)
|
116
|
+
version_string = version.truncate(:pre).to_s
|
117
|
+
File.open(path, "w") { |f|
|
118
|
+
f.puts "--"
|
119
|
+
f.puts "-- This file is auto-generated by prick(1). Please don't touch"
|
120
|
+
f.puts COPY_STMT
|
121
|
+
f.print \
|
122
|
+
"1\t",
|
123
|
+
FIELDS[..-2].map { |f| version.send(f.to_sym) || '\N' }.join("\t"),
|
124
|
+
"\t#{version_string}\n"
|
125
|
+
f.puts "\\."
|
126
|
+
}
|
127
|
+
Git.add(path)
|
128
|
+
end
|
129
|
+
|
130
|
+
def create() raise Internal, "This should not happen" end
|
131
|
+
|
132
|
+
private
|
133
|
+
FIELDS = %w(custom major minor patch pre feature version)
|
134
|
+
COPY_STMT = "COPY prick.versions (id, #{FIELDS.join(', ')}) FROM stdin;"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
data/lib/prick/version.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
|
2
|
-
#
|
2
|
+
# "require 'semantic'" is moved to lib/prick.rb to avoid having Gem depend on it
|
3
3
|
# require 'semantic' # https://github.com/jlindsey/semantic
|
4
4
|
|
5
5
|
# Required by gem
|
6
6
|
module Prick
|
7
|
-
VERSION = "0.
|
7
|
+
VERSION = "0.3.0"
|
8
8
|
end
|
9
9
|
|
10
10
|
# Project related code starts here
|
11
11
|
module Prick
|
12
12
|
class Version
|
13
|
+
class FormatError < RuntimeError; end
|
14
|
+
|
13
15
|
include Comparable
|
14
16
|
|
15
17
|
PRE_LABEL = "pre"
|
@@ -18,9 +20,8 @@ module Prick
|
|
18
20
|
def self.zero() Version.new("0.0.0") end
|
19
21
|
def zero?() self == Version.zero end
|
20
22
|
|
21
|
-
# Return true if `string` is a version
|
22
|
-
|
23
|
-
def self.version?(string) (string =~ VERSION_RE).nil? ? false : true end
|
23
|
+
# Return true if `string` is a version
|
24
|
+
def self.version?(string) !(string =~ VERSION_RE).nil? end
|
24
25
|
|
25
26
|
attr_accessor :custom
|
26
27
|
attr_accessor :semver
|
@@ -41,8 +42,8 @@ module Prick
|
|
41
42
|
# Return true if this is a feature release
|
42
43
|
def feature?() !@feature.nil? end
|
43
44
|
|
44
|
-
# Return true if this is a release branch
|
45
|
-
def release?() !feature? end
|
45
|
+
# Return true if this is a release branch (and not a prerelease)
|
46
|
+
def release?() !feature? && !pre? end
|
46
47
|
|
47
48
|
# Return true if this is a pre-release
|
48
49
|
def pre?() !@semver.pre.nil? end
|
@@ -63,8 +64,7 @@ module Prick
|
|
63
64
|
def initialize(version, custom: nil, feature: nil)
|
64
65
|
case version
|
65
66
|
when String
|
66
|
-
version =~ VERSION_RE
|
67
|
-
self.class.version?(version) or raise "Expected a version, got #{version.inspect}"
|
67
|
+
version =~ VERSION_RE or raise Version::FormatError, "Expected a version, got #{version.inspect}"
|
68
68
|
@custom = custom || $1
|
69
69
|
@semver = Semantic::Version.new($2)
|
70
70
|
@feature = feature || $3
|
@@ -77,20 +77,28 @@ module Prick
|
|
77
77
|
@semver = version.semver.dup
|
78
78
|
@feature = feature || version.feature
|
79
79
|
else
|
80
|
-
raise "Expected a String, Version, or Semantic::Version, got #{version.class}"
|
80
|
+
raise Internal, "Expected a String, Version, or Semantic::Version, got #{version.class}"
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
# Try converting the string `version` into a Version object. Return nil if unsuccessful
|
85
|
+
def self.try(version)
|
86
|
+
version?(version) ? new(version) : nil
|
87
|
+
end
|
88
|
+
|
84
89
|
# `part` can be one of :major, :minor, :patch, or :pre. If pre is undefined, it
|
85
90
|
# is set to `pre_initial_value`
|
86
91
|
def increment(part, pre_initial_value = 1)
|
92
|
+
self.dup.increment!(part, pre_initial_value)
|
93
|
+
end
|
94
|
+
|
95
|
+
def increment!(part, pre_initial_value = 1)
|
87
96
|
if part == :pre
|
88
|
-
|
89
|
-
v.pre = (v.pre ? v.pre+1 : pre_initial_value)
|
90
|
-
v
|
97
|
+
self.pre = (self.pre ? self.pre + 1 : pre_initial_value)
|
91
98
|
else
|
92
|
-
|
99
|
+
@semver = semver.increment!(part)
|
93
100
|
end
|
101
|
+
self
|
94
102
|
end
|
95
103
|
|
96
104
|
def truncate(part)
|
@@ -109,6 +117,18 @@ module Prick
|
|
109
117
|
end
|
110
118
|
end
|
111
119
|
|
120
|
+
def tag() "v#{to_s}" end
|
121
|
+
|
122
|
+
def path
|
123
|
+
parts = [FEATURE_DIR, truncate(:pre), feature].compact
|
124
|
+
File.join(*parts)
|
125
|
+
end
|
126
|
+
|
127
|
+
def link
|
128
|
+
!feature? or raise Internal, "Version #{to_s} is a feature, not a release"
|
129
|
+
File.join(RELEASE_DIR, to_s)
|
130
|
+
end
|
131
|
+
|
112
132
|
def <=>(other)
|
113
133
|
r = (custom || "") <=> (other.custom || "")
|
114
134
|
return r if r != 0
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/bash
|
2
|
+
|
3
|
+
# NAME
|
4
|
+
# strip-comments - Remove comments from postgres files
|
5
|
+
#
|
6
|
+
# USAGE
|
7
|
+
# strip-comments [FILE]
|
8
|
+
#
|
9
|
+
# DESCRIPTION
|
10
|
+
# Remove comments and blank lines from standard intput or the given file and
|
11
|
+
# write the result on standard output
|
12
|
+
#
|
13
|
+
# REFERENCES
|
14
|
+
# https://stackoverflow.com/a/35708616/1745001
|
15
|
+
#
|
16
|
+
|
17
|
+
PROGRAM=$(basename $0)
|
18
|
+
USAGE="[FILE]"
|
19
|
+
|
20
|
+
function error() {
|
21
|
+
echo "$PROGRAM: $@"
|
22
|
+
echo "Usage: $PROGRAM $USAGE"
|
23
|
+
exit 1
|
24
|
+
} >&2
|
25
|
+
|
26
|
+
[ $# -le 1 ] || error "Illegal number of arguments"
|
27
|
+
FILE=$1
|
28
|
+
|
29
|
+
sed '/^\s*--/d' $FILE \
|
30
|
+
| sed 's/a/aA/g; s/__/aB/g; s/#/aC/g' \
|
31
|
+
| gcc -P -E -ansi - \
|
32
|
+
| sed 's/aC/#/g; s/aB/__/g; s/aA/a/g'
|
33
|
+
|