prick 0.3.0 → 0.8.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/TODO +7 -0
  4. data/exe/prick +235 -163
  5. data/lib/ext/fileutils.rb +7 -0
  6. data/lib/prick.rb +7 -5
  7. data/lib/prick/builder.rb +31 -8
  8. data/lib/prick/cache.rb +34 -0
  9. data/lib/prick/constants.rb +106 -54
  10. data/lib/prick/database.rb +26 -20
  11. data/lib/prick/diff.rb +103 -25
  12. data/lib/prick/git.rb +31 -9
  13. data/lib/prick/head.rb +183 -0
  14. data/lib/prick/migration.rb +41 -181
  15. data/lib/prick/program.rb +255 -0
  16. data/lib/prick/project.rb +264 -0
  17. data/lib/prick/rdbms.rb +3 -12
  18. data/lib/prick/schema.rb +6 -11
  19. data/lib/prick/state.rb +129 -74
  20. data/lib/prick/version.rb +41 -28
  21. data/prick.gemspec +3 -1
  22. data/share/diff/diff.after-tables.sql +4 -0
  23. data/share/diff/diff.before-tables.sql +4 -0
  24. data/share/diff/diff.tables.sql +8 -0
  25. data/share/migration/diff.tables.sql +8 -0
  26. data/share/{release_migration → migration}/features.yml +0 -0
  27. data/share/migration/migrate.sql +3 -0
  28. data/share/{release_migration → migration}/migrate.yml +3 -0
  29. data/share/migration/tables.sql +3 -0
  30. data/share/{schemas → schema/schema}/build.yml +0 -0
  31. data/share/{schemas → schema/schema}/prick/build.yml +0 -0
  32. data/share/schema/schema/prick/data.sql +7 -0
  33. data/share/{schemas → schema/schema}/prick/schema.sql +0 -0
  34. data/share/{schemas → schema/schema}/prick/tables.sql +2 -2
  35. data/share/{schemas → schema/schema}/public/.keep +0 -0
  36. data/share/{schemas → schema/schema}/public/build.yml +0 -0
  37. data/share/{schemas → schema/schema}/public/schema.sql +0 -0
  38. data/test_refactor +34 -0
  39. metadata +23 -21
  40. data/file +0 -0
  41. data/lib/prick/build.rb +0 -376
  42. data/lib/prick/migra.rb +0 -22
  43. data/share/feature_migration/diff.sql +0 -2
  44. data/share/feature_migration/migrate.sql +0 -2
  45. data/share/release_migration/diff.sql +0 -3
  46. data/share/release_migration/migrate.sql +0 -5
  47. data/share/schemas/prick/data.sql +0 -7
data/lib/prick/rdbms.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'prick/command.rb'
2
- #require 'prick/ensure.rb'
3
2
 
4
3
  require 'csv'
5
4
 
@@ -15,7 +14,9 @@ module Prick
15
14
  {
16
15
  echo "set role #{user};"
17
16
  echo "set search_path to public;"
18
- echo "#{sql}"
17
+ cat <<'EOF'
18
+ #{sql}
19
+ EOF
19
20
  } | psql --csv --tuples-only --quiet -v ON_ERROR_STOP=1 -d #{db}
20
21
  )
21
22
  CSV.new(stdout.join("\n")).read
@@ -131,16 +132,6 @@ module Prick
131
132
  data_opt = (data ? "" : "--schema-only")
132
133
  Command.command "pg_dump --no-owner #{data_opt} #{db} | gzip --to-stdout >#{file}"
133
134
  end
134
-
135
- private
136
- @ensure_states = {
137
- exist_user: [:create_user, :drop_user],
138
- exist_database: [
139
- lambda { |this, db, user| this.ensure_state(:exist_user, user) },
140
- lambda { |this, db, user| this.create_database(db, owner: user) },
141
- :drop_database
142
- ]
143
- }
144
135
  end
145
136
  end
146
137
 
data/lib/prick/schema.rb CHANGED
@@ -7,24 +7,19 @@ module Prick
7
7
  BUILD_SQL_FILE = BUILD_BASE_NAME + ".sql"
8
8
  BUILD_YML_FILE = BUILD_BASE_NAME + ".yml"
9
9
 
10
- # Note this models the SCHEMAS_DIR directory, not a database schema
10
+ # Note this models the SCHEMA_DIR directory, not a database schema
11
11
  class Schema
12
- attr_reader :directory
13
- def yml_file() SchemaBuilder.yml_file(directory) end
12
+ def yml_file() SchemaBuilder.yml_file(SCHEMA_DIR) end
14
13
 
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
18
-
19
- def initialize(directory = SCHEMAS_DIR)
20
- @directory = directory
21
- end
14
+ def version() SchemaVersion.load end
15
+ def version=(version) SchemaVersion.new(version).write end
16
+ def version_file() SchemaVersion.new.path end
22
17
 
23
18
  def built?(database) database.exist? && database.version == version end
24
19
 
25
20
  # `subject` can be a subpath of schema/ (ie. 'public/tables')
26
21
  def build(database, subject = nil)
27
- SchemaBuilder.new(database, directory).build(subject)
22
+ SchemaBuilder.new(database, SCHEMA_DIR).build(subject)
28
23
  end
29
24
  end
30
25
  end
data/lib/prick/state.rb CHANGED
@@ -2,25 +2,119 @@
2
2
  require 'yaml'
3
3
 
4
4
  module Prick
5
+ # General interface for prick state files. Comments are automatically removed
5
6
  class PrickFile
6
7
  attr_reader :path
7
8
  def initialize(path) @path = path end
8
9
  def exist?() File.exist?(path) end
10
+ def self.exist?() self.new.exist? end # Require an #initializer with no arguments
11
+
12
+ def self.load(*args) self.new(*args).read end
13
+
14
+ def self.read() raise NotThis end
9
15
 
10
16
  protected
17
+ # Read file from disk or from the given branch or tag
11
18
  def general_read(method, branch: nil, tag: nil)
19
+ !(branch && tag) or raise Internal, "Not both of `branch` and `tag` can be defined"
12
20
  branch || tag ? Git.send(method, path, branch: branch, tag: tag) : File.open(path, "r").send(method)
13
21
  end
22
+
14
23
  def do_read(**opts) general_read(:read, **opts) end
15
- def do_readlines(**opts) general_read(:readlines, **opts) end
24
+ def do_readline(**opts) do_readlines(**opts).first end
25
+ def do_readlines(**opts) general_read(:readlines, **opts).reject { |l| l =~ /^\s*#/ } end
26
+ end
27
+
28
+ # Models the .prick-version file. It contains just one line with the version
29
+ # of prick itself
30
+ class PrickVersion < PrickFile
31
+ def initialize() super PRICK_VERSION_FILE end
32
+
33
+ # Return the version
34
+ def read(branch: nil, tag: nil)
35
+ Version.new(do_readline(branch: branch, tag: tag).chomp.sub(/^prick-/, ""))
36
+ end
37
+
38
+ # Write prick version
39
+ def write(version)
40
+ File.open(path, "w") { |file| file.puts "prick-#{version}" }
41
+ end
42
+ end
43
+
44
+ # Models the schema version that is stored in the data.sql file in the prick
45
+ # schema directory. Note that SchemaVersion caches the version. Use #clear to
46
+ # reset the cache
47
+ class SchemaVersion < PrickFile
48
+ def initialize(version = nil)
49
+ @version = version
50
+ super SCHEMA_VERSION_PATH
51
+ end
52
+
53
+ def clear() @version = nil end
54
+
55
+ def create() raise Internal, "This should not happen" end
56
+
57
+ def read(**opts)
58
+ return @version if @version
59
+ lines = do_readlines
60
+ while !lines.empty?
61
+ line = lines.shift.chomp
62
+ next if line != COPY_STMT
63
+ data = lines.shift.chomp
64
+ a = data.split("\t").map { |val| parse_sql_literal(val) }
65
+ a.size == FIELDS.size + 1 or raise Fail, "Illegal data format in #{path}"
66
+ fork, major, minor, patch, pre, feature, version = *a[1..-1]
67
+ @version = Version.new("0.0.0", fork: fork, feature: feature)
68
+ @version.major = major
69
+ @version.minor = minor
70
+ @version.patch = patch
71
+ @version.pre = pre
72
+ return @version
73
+ end
74
+ raise Fail, "No COPY statement in #{path}"
75
+ end
76
+
77
+ def write(version = @version)
78
+ # puts "Writing #{path}"
79
+ version_string = version.truncate(:pre).to_s
80
+ File.open(path, "w") { |f|
81
+ f.puts "--"
82
+ f.puts "-- This file is auto-generated by prick(1). Please don't touch"
83
+ f.puts COPY_STMT
84
+ f.print \
85
+ "1\t",
86
+ FIELDS[..-2].map { |f| version.send(f.to_sym) || '\N' }.join("\t"),
87
+ "\t#{version_string}\n"
88
+ f.puts "\\."
89
+ }
90
+ Git.add(path)
91
+ version
92
+ end
93
+
94
+ private
95
+ FIELDS = %w(fork major minor patch pre feature version)
96
+ COPY_STMT = "COPY prick.versions (id, #{FIELDS.join(', ')}) FROM stdin;"
97
+
98
+ def parse_sql_literal(s)
99
+ case s
100
+ when '\N', 'null'; nil
101
+ when/^\d+$/; s.to_i
102
+ else
103
+ s
104
+ end
105
+ end
16
106
  end
17
107
 
108
+ # General interface for prick state files in YAML format
18
109
  class State < PrickFile
19
- # `fields` is a Hash from field name (Symbol) to field type (class). Eg. { f: Integer }
110
+ # `fields` is a Hash from field name (Symbol) to field type (class) where a
111
+ # class can be a class known to YAML (Integer, String, etc.) or Version.
112
+ # The #initialize method generates an accessor methods for each field
20
113
  def initialize(path, **fields)
21
114
  super(path)
22
115
  @fields = fields
23
116
  @fields.each_key { |k| self.class.attr_accessor k }
117
+ @loaded = false
24
118
  end
25
119
 
26
120
  def create() write end
@@ -29,23 +123,23 @@ module Prick
29
123
  for field, value in fields
30
124
  self.send(:"#{field}=", value)
31
125
  end
126
+ self
32
127
  end
33
128
 
34
129
  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
130
+ return self if @loaded
131
+ hash = YAML.load(do_read(branch: branch, tag: tag))
132
+ for field, klass in @fields
133
+ value = hash[field.to_s]
134
+ value = Version.new(value) if klass == Version && !value.nil?
135
+ self.instance_variable_set("@#{field}", value)
44
136
  end
137
+ @loaded = true
45
138
  self
46
139
  end
47
140
 
48
- def write
141
+ def write(**fields)
142
+ set(**fields)
49
143
  hash = @fields.map { |field, klass|
50
144
  value = self.send(field)
51
145
  value = value.to_s if klass == Version && !value.nil?
@@ -58,80 +152,41 @@ module Prick
58
152
  end
59
153
 
60
154
  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)
155
+ def initialize(version: nil, base_version: nil)
156
+ super(PRICK_MIGRATION_PATH, version: Version, base_version: Version)
63
157
  set(version: version, base_version: base_version)
64
158
  end
65
159
  end
66
160
 
67
- class FeaturesState < State
68
- def initialize(directory, features: [])
69
- super(File.join(directory, FEATURES_STATE_FILE), features: Array)
70
- set(features: features)
161
+ class HeadState < State
162
+ def initialize(name: nil)
163
+ super(PRICK_HEAD_PATH, name: String)
164
+ set(name: name)
71
165
  end
72
166
  end
73
167
 
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)
168
+ class FeatureState < State
169
+ def initialize(feature: nil)
170
+ super(PRICK_FEATURE_PATH, feature: String)
171
+ set(feature: feature)
78
172
  end
79
173
  end
80
174
 
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
175
+ # class FeaturesState < State
176
+ # def initialize(directory, features: [])
177
+ # super(File.join(directory, FEATURES_STATE_FILE), features: Array)
178
+ # set(features: features)
179
+ # end
180
+ # end
181
+ #
87
182
 
88
- def write(version)
89
- File.open(path, "w") { |file| file.puts "prick-#{version}" }
183
+ # Models the .prick-project file that contains the name of the project and
184
+ # the database user
185
+ class ProjectState < State
186
+ def initialize(name: nil, user: nil)
187
+ super(PROJECT_STATE_FILE, name: String, user: String)
188
+ set(name: name, user: user)
90
189
  end
91
190
  end
92
191
 
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
192
  end
137
-
data/lib/prick/version.rb CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Required by gem
6
6
  module Prick
7
- VERSION = "0.3.0"
7
+ VERSION = "0.8.0"
8
8
  end
9
9
 
10
10
  # Project related code starts here
@@ -21,9 +21,12 @@ module Prick
21
21
  def zero?() self == Version.zero end
22
22
 
23
23
  # Return true if `string` is a version
24
- def self.version?(string) !(string =~ VERSION_RE).nil? end
24
+ def self.version?(string)
25
+ string.is_a?(String) or raise Internal, "String expected"
26
+ !(string =~ VERSION_RE).nil?
27
+ end
25
28
 
26
- attr_accessor :custom
29
+ attr_accessor :fork
27
30
  attr_accessor :semver
28
31
  attr_accessor :feature
29
32
 
@@ -36,8 +39,8 @@ module Prick
36
39
  def patch() @semver.patch end
37
40
  def patch=(patch) @semver.patch = patch end
38
41
 
39
- # Return true if this is a custom release
40
- def custom?() !@custom.nil? end
42
+ # Return true if this is a fork release
43
+ def fork?() !@fork.nil? end
41
44
 
42
45
  # Return true if this is a feature release
43
46
  def feature?() !@feature.nil? end
@@ -47,13 +50,16 @@ module Prick
47
50
 
48
51
  # Return true if this is a pre-release
49
52
  def pre?() !@semver.pre.nil? end
53
+ def prerelease?() pre? end
50
54
 
51
55
  # The releases is stored as a String (eg. 'pre.1') in the semantic version
52
56
  # but #pre returns only the Integer number
53
57
  def pre() @semver.pre =~ PRE_RE ? $1.to_i : nil end
58
+ def prerelease() pre end
54
59
 
55
60
  # #pre= expects an integer or nil argument
56
61
  def pre=(pre) @semver.pre = (pre ? "#{PRE_LABEL}.#{pre}" : nil) end
62
+ def prerelease=(pre) self.pre = pre end
57
63
 
58
64
  def dup() Version.new(self) end
59
65
  def clone() Version.new(self) end
@@ -61,19 +67,19 @@ module Prick
61
67
  def eql?(other) self == other end
62
68
  def hash() @semver.hash end
63
69
 
64
- def initialize(version, custom: nil, feature: nil)
70
+ def initialize(version, fork: nil, feature: nil)
65
71
  case version
66
72
  when String
67
73
  version =~ VERSION_RE or raise Version::FormatError, "Expected a version, got #{version.inspect}"
68
- @custom = custom || $1
69
- @semver = Semantic::Version.new($2)
70
- @feature = feature || $3
74
+ @fork = fork || $1
75
+ @semver = Semantic::Version.new($3)
76
+ @feature = feature || $4
71
77
  when Semantic::Version
72
- @custom = custom
78
+ @fork = fork
73
79
  @semver = version.dup
74
80
  @feature = feature
75
81
  when Version
76
- @custom = custom || version.custom
82
+ @fork = fork || version.fork
77
83
  @semver = version.semver.dup
78
84
  @feature = feature || version.feature
79
85
  else
@@ -83,9 +89,17 @@ module Prick
83
89
 
84
90
  # Try converting the string `version` into a Version object. Return nil if unsuccessful
85
91
  def self.try(version)
86
- version?(version) ? new(version) : nil
92
+ version.is_a?(Version) ? version : (version?(version) ? new(version) : nil)
87
93
  end
88
94
 
95
+ # Parse a branch or tag name into a Version object. Return a [version, tag]
96
+ # tuple where tag is true if name was a tag
97
+ def self.parse(name)
98
+ name =~ VERSION_RE or raise Version::FormatError, "Expected a version, got #{version.inspect}"
99
+ fork, tag, semver, feature = $1, $2, $3, $4
100
+ version = Version.new(semver, fork: fork, feature: feature)
101
+ [version, tag]
102
+ end
89
103
  # `part` can be one of :major, :minor, :patch, or :pre. If pre is undefined, it
90
104
  # is set to `pre_initial_value`
91
105
  def increment(part, pre_initial_value = 1)
@@ -117,20 +131,18 @@ module Prick
117
131
  end
118
132
  end
119
133
 
120
- def tag() "v#{to_s}" end
134
+ # def path
135
+ # parts = [FEATURE_DIR, truncate(:pre), feature].compact
136
+ # File.join(*parts)
137
+ # end
121
138
 
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
139
+ # def link
140
+ # !feature? or raise Internal, "Version #{to_s} is a feature, not a release"
141
+ # File.join(RELEASE_DIR, to_s)
142
+ # end
131
143
 
132
144
  def <=>(other)
133
- r = (custom || "") <=> (other.custom || "")
145
+ r = (fork || "") <=> (other.fork || "")
134
146
  return r if r != 0
135
147
  r = semver <=> other.semver
136
148
  return r if r != 0
@@ -138,16 +150,17 @@ module Prick
138
150
  return r
139
151
  end
140
152
 
141
- # Render as String
142
- def to_s
143
- (custom ? "#{custom}-" : "") + semver.to_s + (feature ? "_#{feature}" : "")
153
+ # Render as branch
154
+ def to_s(tag: false)
155
+ (fork ? "#{fork}-" : "") + (tag ? "v" : "") + semver.to_s + (feature ? "_#{feature}" : "")
144
156
  end
145
157
 
158
+ # Render as a tag
159
+ def to_tag() to_s(tag: true) end
160
+
146
161
  # Render as string
147
162
  def inspect() to_s end
148
163
  end
149
-
150
- # ZERO = Version.new("0.0.0")
151
164
  end
152
165
 
153
166