prick 0.15.0 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65c56eb6650acf4a9a15acc00589075fe551548f06df7f714db56b89fc8f0f7d
4
- data.tar.gz: 585b320426243c5a3a82df87a924e89e03807c47b618a44350a8c7e646112f34
3
+ metadata.gz: 75523a625d7139830b419f2624f96af3c029dc1ad89b0cb40dec289f4b94b522
4
+ data.tar.gz: 450c53de9ef6b3c8627df7471076b87c559f367d7aef30e009a846bffa98390c
5
5
  SHA512:
6
- metadata.gz: 72f83cb2412de38665afac3c6b3e7bd293d17ad35c64910bec6ef6db5080922d5d6771b044b76dcc52539da60327843861f681f979f3e04739a3cb04f804f8e1
7
- data.tar.gz: c5460d78662709f1c5cb8bc5badd3243230cd41ff800b3d8fb9eee3d3e49cd314f51548dab060f0435d5c1aa58c3dbdfb22c490acc96b8e4dc101659675af027
6
+ metadata.gz: 98b24c53c2ae54007c46cb4157963ba049327e6ea9b73c5b6b8502367e58d9d018acdb74c9896394bd90a0335359719c0e02523f1e2965de1ed31c023285eeb9
7
+ data.tar.gz: 25b7425c7f8547bdd0be1ee0a570b1b6115f08ff2ba3bd8c8284cced0ffc5da1ecbc0670e57bb21d2b9a45276df03d0f2f3210b83712ff50f55f811991ba11e3
data/TODO CHANGED
@@ -1,4 +1,7 @@
1
1
 
2
+ o Add a top-level 'global' directory for stuff that doesn't belong to a schema or that
3
+ require superuser privileges. prick-build should then execute the content of that
4
+ directory first before switching to the database user
2
5
  o Check for commits to tags
3
6
  o Accumulate version history in prick.versions instead of just having the newest
4
7
  o Using rc's in migration syntax: fork-version-fork-version.rc1 ?
data/exe/prick CHANGED
@@ -10,6 +10,7 @@ require 'tempfile'
10
10
  include ShellOpts
11
11
  include Prick
12
12
 
13
+
13
14
  SPEC = %(
14
15
  -n,name=NAME
15
16
  Name of project. Defauls to the environment variable `PRICK_PROJECT` if
@@ -51,11 +52,13 @@ SPEC = %(
51
52
  list.cache!
52
53
  List cache files
53
54
 
54
- build! -d,database=DATABASE -C,no-cache [TAG]
55
- Build the current database from the content in the schemas/ directory.
56
- With a tag the version is built into the associated versioned database
57
- and the result is saved to cache unless the -C option is given. The -d
58
- option overrides the default database
55
+ build! -d,database=DATABASE -s,state=FILE -C,no-cache [TAG]
56
+ Drop all users associated with the database before building the current
57
+ database from the content in the schemas/ directory. With a tag the
58
+ version is built into the associated versioned database and the result is
59
+ saved to cache unless the -C option is given. The -d option overrides the
60
+ default database and the -s option overrides the default state file
61
+ (fox.state)
59
62
 
60
63
  make! -d,database=DATABASE -C,no-cache [TAG]
61
64
  Build the current database from the content in the schemas/ directory.
@@ -77,8 +80,12 @@ SPEC = %(
77
80
  given file
78
81
 
79
82
  drop! -a,all [DATABASE]
80
- Drop the given database or all versioned databases. The --all option also
81
- drops the project database
83
+ Drop the given database or all versioned databases. Users with a username
84
+ on the form <database>__<username> are also dropped. The --all option
85
+ also drops the project database
86
+
87
+ drop.users! [DATABASE]
88
+ Drop users with a username on the form <database>__<username>
82
89
 
83
90
  diff! -m,mark -t,tables -T,notables
84
91
  diff [FROM-DATABASE|FROM-VERSION [TO-DATABASE|TO-VERSION]]
@@ -153,7 +160,7 @@ SPEC = %(
153
160
  in the var/spool directory
154
161
  )
155
162
 
156
-
163
+ DEFAULT_STATE_FILE = "fox.state"
157
164
 
158
165
  opts, args = ShellOpts.process(SPEC, ARGV)
159
166
 
@@ -185,6 +192,7 @@ begin
185
192
 
186
193
  # Create program object
187
194
  program = Program.new(quiet: opts.quiet?, verbose: opts.verbose?)
195
+ $verbose = opts.verbose? ? opts.verbose : nil
188
196
 
189
197
  # Handle init command
190
198
  if opts.subcommand == :init
@@ -198,7 +206,7 @@ begin
198
206
  # Change to parent directory containing the Prick version file if not found
199
207
  # in the current directory
200
208
  program.current_directory = Dir.getwd
201
- while !Dir.getwd != "/" && !File.exist?(PRICK_VERSION_FILE)
209
+ while Dir.getwd != "/" && !File.exist?(PRICK_VERSION_FILE)
202
210
  Dir.chdir("..")
203
211
  end
204
212
 
@@ -236,7 +244,9 @@ begin
236
244
 
237
245
  when :build
238
246
  version = args.expect(0..1)
239
- program.build(opts.build!.database, version, opts.build!.no_cache?)
247
+ state_file = File.expand_path(opts.build!.state || DEFAULT_STATE_FILE)
248
+ FileUtils.rm_f(state_file)
249
+ program.build(opts.build!.database, version, state_file, opts.build!.no_cache?)
240
250
 
241
251
  when :make
242
252
  command = opts.make!
@@ -258,7 +268,15 @@ begin
258
268
  program.save(version, file)
259
269
 
260
270
  when :drop
261
- program.drop(args.expect(0..1), opts.drop!.all?)
271
+ command = opts.drop!
272
+ case command.subcommand
273
+ when :users
274
+ database = args.extract(0..1) || program.project.database.name
275
+ args.expect(0)
276
+ program.drop_users(database)
277
+ else
278
+ program.drop(args.expect(0..1), opts.drop!.all?)
279
+ end
262
280
 
263
281
  when :diff
264
282
  mark = opts.diff!.mark
data/lib/prick/branch.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require "prick/state.rb"
2
2
 
3
+ # FIXME FIXME FIXME Not used!
4
+
5
+
3
6
  module Prick
4
7
  class Branch
5
8
  # Branch name. It is usually equal to the version but migrations use a
@@ -116,6 +119,14 @@ module Prick
116
119
  end
117
120
  end
118
121
 
122
+ # A user defined branch. User defined branches have no version and only the
123
+ # #build method is defined TODO: Make all methods but #build private
124
+ class UserBranch < Branch
125
+ def initialize(name)
126
+ super(name, nil, nil, nil, nil)
127
+ end
128
+ end
129
+
119
130
  class AbstractRelease < Branch
120
131
  def create(schema_version = self.version)
121
132
  super()
data/lib/prick/builder.rb CHANGED
@@ -1,20 +1,23 @@
1
1
  require "prick/state.rb"
2
2
 
3
+ require 'indented_io'
4
+
3
5
  module Prick
4
6
  # Builder is a procedural object for building schemas and executing
5
7
  # migrations. Builder use resources that can be executables, SQL files, YAML
6
- # files, or directories. A resource is identified by a name (eg. 'my-tables')
7
- # and is used to match a build file. Build files are looked up in the
8
- # following order:
8
+ # files, FOX files, or directories. A resource is identified by a name (eg.
9
+ # 'my-tables') and is used to match a build file. Build files are looked up
10
+ # in the following order:
9
11
  #
10
12
  # #{name} executable
11
13
  # #{name}.* executable
12
14
  # #{name}.yml
13
15
  # #{name}.sql
16
+ # #{name}.fox
14
17
  # #{name}/
15
18
  #
16
19
  # The output from executable objects is expected to be SQL statements that
17
- # are fed into postgres
20
+ # are then fed into postgres
18
21
  #
19
22
  # When a resource match a directory, the directory can contain a special
20
23
  # default resource ('build' or 'migrate') that takes over the rest of the
@@ -23,8 +26,8 @@ module Prick
23
26
  # directory doesn't contain a build resource, the resources in the directory
24
27
  # are built in alphabetic order
25
28
  #
26
- # Build (but not migration) SQL scripts and executables are executed with
27
- # search path set to containing schema
29
+ # Build SQL scripts and executables are executed with search path set to
30
+ # containing schema. This doesn't include migration script or executable
28
31
  #
29
32
  class Builder
30
33
  attr_reader :database
@@ -32,6 +35,11 @@ module Prick
32
35
  attr_reader :default # either "build" or "migrate"
33
36
  attr_reader :lines
34
37
 
38
+ def state_file() self.class.state_file end
39
+ def state_file=(file) self.class.state_file = file end
40
+ def self.state_file() @@state_file end
41
+ def self.state_file=(file) @@state_file = file end
42
+
35
43
  def initialize(database, directory, default)
36
44
  @database = database
37
45
  @directory = directory
@@ -42,9 +50,9 @@ module Prick
42
50
  end
43
51
 
44
52
  def build(subject = default, execute: true)
53
+ puts "build(#{subject.inspect}, execute: #{execute.inspect})" if $verbose > 0
45
54
  @execute = execute
46
55
  @lines = []
47
- # puts "Building #{subject.inspect}"
48
56
  Dir.chdir(directory) {
49
57
  if subject
50
58
  build_subject(subject)
@@ -59,14 +67,23 @@ module Prick
59
67
  def self.yml_file(directory) raise NotThis end
60
68
 
61
69
  protected
62
- def do_dir(path, &block)
63
- dir, file = File.split(path)
64
- Dir.chdir(dir) { yield(file) }
70
+ def do_path(path, &block)
71
+ puts "do_path(#{path.inspect})" if $verbose >= 3
72
+ if File.directory?(path)
73
+ dir, file = path, nil
74
+ else
75
+ dir, file = File.split(path)
76
+ end
77
+ if $verbose >= 2
78
+ indent { Dir.chdir(dir) { yield(file) } }
79
+ else
80
+ Dir.chdir(dir) { yield(file) }
81
+ end
65
82
  end
66
83
 
67
84
  def do_sql(path, schema: nil)
68
- # puts "do_sql(#{path})"
69
- do_dir(path) { |file|
85
+ puts "do_sql(#{path})" if $verbose >= 2
86
+ do_path(path) { |file|
70
87
  if @execute
71
88
  begin
72
89
  Rdbms.exec_file(database.name, file, user: database.user, schema: schema)
@@ -82,10 +99,29 @@ module Prick
82
99
  true
83
100
  end
84
101
 
85
- def do_exe(path, schema: nil)
86
- # puts "do_exe(#{path})"
87
- do_dir(path) { |file|
88
- lines = Command.command "./#{file} #{database.name} #{database.user}" # FIXME Security
102
+ def do_fox(path, schema: nil)
103
+ puts "do_fox(#{path.inspect}, #{schema.inspect})" if $verbose >= 2
104
+ do_path(path) { |file|
105
+ lines = Command.command "fox --state=#{state_file} --write --delete=none #{database.name} #{file}"
106
+ if @execute
107
+ begin
108
+ Rdbms.exec_sql(database.name, lines.join("\n"), user: database.user, schema: schema)
109
+ rescue Command::Error => ex
110
+ $stderr.puts ex.stderr
111
+ $stderr.puts "from #{reldir}/#{file}"
112
+ exit 1
113
+ end
114
+ else
115
+ @lines += lines
116
+ end
117
+ }
118
+ end
119
+
120
+ def do_exe(path, args = [], schema: nil)
121
+ puts "do_exe(#{path.inspect}, #{args.inspect})" if $verbose >= 2
122
+ do_path(path) { |file|
123
+ cmd = (["./#{file}"] + args).join(" ")
124
+ lines = Command.command cmd, stdin: [database.name, database.user], stderr: nil
89
125
  if @execute
90
126
  begin
91
127
  Rdbms.exec_sql(database.name, lines.join("\n"), user: database.user, schema: schema)
@@ -102,20 +138,25 @@ module Prick
102
138
  end
103
139
 
104
140
  def do_yml(path)
105
- # puts "do_yml(#{path})"
106
- dir, file = File.split(path)
107
- YAML.load(File.read(path))&.each { |subject| build_subject(File.join(dir, subject)) }
141
+ puts "do_yml(#{path})" if $verbose >= 2
142
+ do_path(path) { |file|
143
+ YAML.load(File.read(file))&.each { |subject| build_subject(subject) }
144
+ }
108
145
  true
109
146
  end
110
147
 
111
- # A subject can be both an abstract subject or a concrete file (*.yml, *.sql)
148
+ # A subject can be both an abstract subject or a concrete file (*.yml, *.sql, or *.fox)
112
149
  def build_subject(subject)
113
- if File.file?(subject) && File.executable?(subject)
114
- do_exe(subject)
150
+ puts "build_subject(#{subject.inspect}) in #{Dir.getwd}" if $verbose > 0
151
+ cmd, *args = subject.split(/\s+/)
152
+ if File.file?(cmd) && File.executable?(cmd)
153
+ do_exe(cmd, args)
115
154
  elsif File.file?(subject) && subject.end_with?(".yml")
116
155
  do_yml(subject)
117
156
  elsif File.file?(subject) && subject.end_with?(".sql")
118
157
  do_sql(subject)
158
+ elsif File.file?(subject) && subject.end_with?(".fox")
159
+ do_fox(subject)
119
160
  elsif File.exist?(yml_file = "#{subject}.yml")
120
161
  do_yml(yml_file)
121
162
  elsif File.exist?(sql_file = "#{subject}.sql")
@@ -128,16 +169,19 @@ module Prick
128
169
  end
129
170
 
130
171
  def build_directory(path)
131
- Dir.chdir(path) {
132
- build_subject(File.join(path, @default)) || begin
133
- if Dir["#{path}/#{default}", "#{path}/#{default}.yml", "#{path}/#{default}.sql"]
172
+ puts "build_directory(#{path.inspect}) in #{Dir.getwd}" if $verbose >= 2
173
+ do_path(path) { |_|
174
+ build_subject(@default) || begin
175
+ if !Dir["#{default}", "#{default}.yml", "#{default}.sql"].empty?
134
176
  subjects = [default]
135
177
  else
178
+ candidates = Dir["*"]
136
179
  exes = candidates.select { |file| File.file?(file) && File.executable?(file) }
137
180
  ymls = candidates.select { |file| file.end_with?(".yml") }.map { |f| f.sub(/\.yml$/, "") }
138
181
  sqls = candidates.select { |file| file.end_with?(".sql") }.map { |f| f.sub(/\.sql$/, "") }
182
+ foxs = candidates.select { |file| file.end_with?(".fox") }.map { |f| f.sub(/\.fox$/, "") }
139
183
  dirs = candidates.select { |file| File.directory?(file) }
140
- subjects = (exes + ymls + sqls + dirs).uniq.sort.reject { |f| f != "diff" }
184
+ subjects = (exes + ymls + sqls + foxs + dirs).uniq.sort #.reject { |f| f != "diff" } FIXME ??
141
185
  end
142
186
  subjects.inject(false) { |a, s| build_subject(s) || a }
143
187
  end
@@ -189,13 +233,13 @@ module Prick
189
233
 
190
234
  protected
191
235
  def do_sql(path)
192
- schema = Dir.getwd.sub(/^.*schema\/([^\/]+)(?:\/.*)?$/, '\1')
236
+ schema = Dir.getwd =~ /^.*schema\/([^\/]+)(?:\/.*)?$/ ? $1 : "public"
193
237
  super(path, schema: schema)
194
238
  end
195
239
 
196
- def do_exe(path)
197
- schema = Dir.getwd.sub(/^.*schema\/([^\/]+)(?:\/.*)?$/, '\1')
198
- super(path, schema: schema)
240
+ def do_exe(path, args = [])
241
+ schema = Dir.getwd =~ /^.*schema\/([^\/]+)(?:\/.*)?$/ ? $1 : "public"
242
+ super(path, args, schema: schema)
199
243
  end
200
244
  end
201
245
  end
data/lib/prick/command.rb CHANGED
@@ -15,14 +15,17 @@ module Command
15
15
  end
16
16
 
17
17
  # Execute the shell command 'cmd' and return standard output as an array of
18
- # strings while stderr is passed through unless stderr: is false. If stderr:
18
+ # strings. while stderr is passed through unless stderr: is false. If stderr:
19
19
  # is true, it returns a tuple of [stdout, stderr] instead. The shell command
20
20
  # is executed with the `errexit` and `pipefail` bash options
21
+ #
22
+ # The :stdin option is a line or an array of lines that'll be fed to the
23
+ # standard input of the command. Default is nil
21
24
  #
22
25
  # It raises a Command::Error exception if the command fails unless :fail is
23
26
  # true. The exit status of the last command is stored in ::status
24
27
  #
25
- def command(cmd, stderr: false, fail: true)
28
+ def command(cmd, stdin: nil, stderr: false, fail: true)
26
29
  cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
27
30
 
28
31
  pw = IO::pipe # pipe[0] for read, pipe[1] for write
@@ -52,12 +55,18 @@ module Command
52
55
  pr[1].close
53
56
  pe[1].close
54
57
 
58
+ if stdin
59
+ pw[1].puts(stdin)
60
+ pw[1].flush
61
+ pw[1].close
62
+ end
63
+
55
64
  @status = Process.waitpid2(pid)[1].exitstatus
56
65
 
57
66
  out = pr[0].readlines.collect { |line| line.chop }
58
- err = pe[0].readlines.collect { |line| line.chop }
67
+ err = pe[0].readlines.collect { |line| line.chop }.grep_v(/^NOTICE:/)
59
68
 
60
- pw[1].close
69
+ pw[1].close if !stdin
61
70
  pr[0].close
62
71
  pe[0].close
63
72
 
@@ -65,7 +74,9 @@ module Command
65
74
  case stderr
66
75
  when true; [out, err]
67
76
  when false; out
68
- when NilClass; $stderr.puts err
77
+ when NilClass;
78
+ $stderr.puts err
79
+ out
69
80
  else
70
81
  raise Internal, "Unexpected value for :stderr - #{stderr.inspect}"
71
82
  end
@@ -61,11 +61,11 @@ module Prick
61
61
 
62
62
  # Hollow-out a database without dropping it. This is useful compared to a
63
63
  # simple drop database since it wont block on other sessions wont block
64
- def clean()
64
+ def clean()
65
65
  if exist?
66
66
  schemas = Rdbms.select_values(
67
- "postgres",
68
- "select nspname from pg_namespace where nspowner != 10")
67
+ name,
68
+ "select nspname from pg_namespace where nspowner != 10 and nspname != 'prick'")
69
69
  for schema in schemas
70
70
  Rdbms.exec_sql(name, "drop schema \"#{schema}\" cascade")
71
71
  end
data/lib/prick/head.rb CHANGED
@@ -120,6 +120,12 @@ module Prick
120
120
  end
121
121
  end
122
122
 
123
+ class UserBranch < Branch
124
+ def initialize(name)
125
+ super(nil, nil, nil)
126
+ end
127
+ end
128
+
123
129
  class ReleaseBranch < Branch
124
130
  def release_branch?() true end
125
131
 
data/lib/prick/program.rb CHANGED
@@ -1,4 +1,5 @@
1
1
 
2
+ require "pg_conn"
2
3
  require "prick.rb"
3
4
 
4
5
  module Prick
@@ -64,12 +65,14 @@ module Prick
64
65
  project.cache.list.each { |l| puts l }
65
66
  end
66
67
 
67
- def build(database, version, nocache)
68
+ def build(database, version, state_file, nocache)
68
69
  into_mesg = database && "into #{database}"
69
70
  version_mesg = version ? "v#{version}" : "current schema"
70
71
  version &&= Version.new(version)
71
72
  version.nil? || Git.tag?(version) or raise Error, "Can't find tag v#{version}"
72
73
  database = database ? Database.new(database, project.user) : project.database(version)
74
+ drop_users(database.name)
75
+ Builder.state_file = state_file
73
76
  project.build(database, version: version)
74
77
  project.save(database) if version && !nocache
75
78
  mesg "Built", version_mesg, into_mesg
@@ -113,6 +116,7 @@ module Prick
113
116
 
114
117
  def drop(database, all)
115
118
  database.nil? || !all or raise Error, "Can't use --all when database is given"
119
+ return if !Rdbms.exist_database?(database)
116
120
  if database
117
121
  check_owned(database)
118
122
  dbs = [database]
@@ -123,10 +127,24 @@ module Prick
123
127
  dbs += Rdbms.list_databases(Prick.tmp_databases_re(project.name)) # FIXME: Only used in dev
124
128
  dbs.each { |db|
125
129
  Rdbms.drop_database(db)
130
+ drop_users db
126
131
  mesg "Dropped database #{db}"
127
132
  }
128
133
  end
129
134
 
135
+ # Drop all users that follows the per-database-user naming convention
136
+ # ("<database>__<username>")
137
+ def drop_users(database)
138
+ PgConn.new("postgres") { |postgres|
139
+ users = postgres.role.list.grep(/^#{database.sub(/-/, "_")}__/)
140
+ if postgres.rdbms.exist?(database)
141
+ PgConn.new(database) { |conn| conn.role.drop users, cascade: true }
142
+ else
143
+ postgres.role.drop users
144
+ end
145
+ }
146
+ end
147
+
130
148
  # `select` is a tri-state variable that can be :tables, :no_tables, or :all
131
149
  # (or any other value)
132
150
  def diff(from, to, mark, select)
@@ -267,240 +285,3 @@ module Prick
267
285
  end
268
286
  end
269
287
 
270
- __END__
271
-
272
-
273
- module Prick
274
- class Program
275
- def project() @project ||= Project.load end
276
-
277
- attr_accessor :quiet
278
- attr_accessor :verbose
279
-
280
- def initialize(quiet: false, verbose: false)
281
- @quiet = quiet
282
- @verbose = verbose
283
- end
284
-
285
- def mesg(*args) puts args.compact.grep(/\S/).join(' ') if !quiet end
286
- def verb(*args) puts args.compact.grep(/\S/).join(' ') if verbose end
287
- def check_clean() Git.clean? or raise Error, "Repository is dirty - please commit your changes first" end
288
-
289
- def initialize_directory(project_name, database_user, directory)
290
- !Project.initialized?(directory) or raise Error, "Directory #{directory} is already initialized"
291
- Project.initialize_directory(project_name, database_user, directory)
292
- if project_name != File.basename(directory)
293
- mesg "Initialized project #{project_name} in #{directory}"
294
- else
295
- mesg "Initialized project #{project_name}"
296
- end
297
- end
298
-
299
- def info
300
- if project.tag?
301
- puts "At v#{project.version} tag"
302
- else
303
- puts "On branch #{project.branch.name}"
304
- end
305
- puts " Git is " + (Git.clean? ? "clean" : "dirty")
306
- bv = project.branch.version
307
- dv = project.database.version
308
- sv = project.branch.schema.version
309
- puts " Database version: #{dv}" + (dv != bv ? " (mismatch)" : "")
310
- puts " Schema version : #{sv}" + (sv != bv ? " (mismatch)" : "")
311
- end
312
-
313
- # TODO: Move to project to take advantage of cache
314
- def build(database, version, no_cache)
315
- version = version && Version.new(version)
316
- into_mesg = database && "into #{database}"
317
- database = database ? Database.new(database, project.user) : project.database(version)
318
- if version
319
- Git.tag?(version) or raise Error, "Can't find tag v#{version}"
320
- cache_file = project.cache_file(version)
321
- if !no_cache && File.exist?(cache_file)
322
- project.load(cache_file, database: database)
323
- mesg "Loaded v#{version}", into_mesg, "from cache"
324
- else
325
- project.build(database: database, version: version)
326
- project.save(cache_file, database: database)
327
- mesg "Built v#{version}", into_mesg
328
- end
329
- else
330
- project.build(database: database)
331
- mesg "Built current schema", into_mesg
332
- end
333
- end
334
-
335
- def load(database, file_or_version)
336
- version = Version.try(file_or_version)
337
- into_mesg = database && "into #{database}"
338
- database = database ? Database.new(database, project.user) : project.database(version)
339
- if version
340
- file = project.cache_file(version)
341
- File.exist?(file) or raise Error, "Can't find #{file} - forgot to build?"
342
- project.load(file, database: database)
343
- mesg "Loaded v#{version}", into_mesg
344
- else
345
- file = file_or_version
346
- project.load(file, database: database)
347
- mesg "Loaded #{file}", into_mesg
348
- end
349
- end
350
-
351
- def save(database, file)
352
- file ||= "#{ENV['USER']}-#{name}-#{branch}.sql.gz"
353
- subject_mesg = database ? "database #{database}" : "current database"
354
- database = database ? Database.new(database, project.user) : project.database(version)
355
- project.save(file, database: database)
356
- mesg "Saved", subject_mesg, "to #{file}"
357
- end
358
-
359
- def make(subject)
360
- project.database.exist? or raise Error, "Project database is not present"
361
- project.make(project.database, subject)
362
- end
363
-
364
- def list_releases(migrations: false, cancelled: false)
365
- puts (project.list_releases(all: cancelled) + (migrations ? project.list_migrations : [])).sort.map(&:name)
366
- end
367
-
368
- def list_migrations
369
- puts project.list_migrations.sort.map(&:name)
370
- end
371
-
372
- def list_upgrades(from = nil, to = nil)
373
- from = from ? Version.new(from) : project.database.version
374
- to = to ? Version.new(to) : project.branch.version
375
- branches = project.list_upgrades(from, to)
376
- puts branches.map(&:name)
377
- end
378
-
379
- def prepare_schema(name)
380
- project.prepare_schema(name)
381
- mesg project.message
382
- end
383
-
384
- def prepare_diff(version = nil)
385
- version ||=
386
- if project.prerelease? || project.migration? || project.feature?
387
- project.branch.base_version
388
- else
389
- project.branch.version
390
- end
391
- project.prepare_diff(version)
392
- mesg "Remember to update the associated SQL migration files"
393
- end
394
-
395
- def prepare_release
396
- check_clean
397
- project.version.release? or raise Error, "You need to be on a release branch to prepare a release"
398
- project.prepare_release
399
- mesg project.message
400
- end
401
-
402
- def check
403
- version ||=
404
- if project.prerelease? || project.migration?
405
- project.branch.base_version
406
- else
407
- project.branch.version
408
- end
409
- project.check_migration(version)
410
- end
411
-
412
- # `arg` can be a version numer of a relative increase (eg. 'minor')
413
- def create_release(arg = nil)
414
- check_clean
415
- if project.release?
416
- arg or raise Error, "Need a version argument"
417
- version = compute_version(project.version, arg)
418
- project.create_release(Version.new(version))
419
- mesg project.message
420
- elsif project.prerelease?
421
- arg.nil? or raise Error, "Illegal number of arguments"
422
- project.create_release_from_prerelease
423
- mesg project.message
424
- else
425
- raise Error, "You need to be on a release or pre-release branch to create a new release"
426
- end
427
- end
428
-
429
- def cancel_release(arg)
430
- project.cancel_release(Version.new(arg))
431
- end
432
-
433
- def create_prerelease(arg)
434
- check_clean
435
- if project.release?
436
- version = %w(major minor patch).include?(arg) ? project.version.increment(arg.to_sym) : Version.new(arg)
437
- project.prepare_release(commit: false)
438
- prerelease = project.create_prerelease(version)
439
- mesg "Created pre-release #{prerelease.version}"
440
- elsif project.prerelease?
441
- arg.nil? or raise Error, "Illegal number of arguments"
442
- prerelease = project.increment_prerelease
443
- mesg "Created pre-release #{prerelease.prerelease_version}"
444
- else
445
- raise Error, "You need to be on a release branch to create a pre-release"
446
- end
447
- end
448
-
449
- def prepare_migration(arg)
450
- check_clean
451
- version = Version.new(arg)
452
- project.release? or raise "You need to be on a release or migration branch to prepare a migration"
453
- project.prepare_migration(version)
454
- mesg project.message
455
- end
456
-
457
- def create_feature(name)
458
- check_clean
459
- project.release? or raise "You ned to be on a release branch to create a feature"
460
- project.create_feature(name)
461
- mesg "Created feature '#{name}'"
462
- end
463
-
464
- def include_feature(name_or_version)
465
- check_clean
466
- project.prerelease? or raise Error, "You need to be on a pre-release branch to include a feature"
467
- version = Version.try(name_or_version) ||
468
- Version.new(project.branch.base_version, feature: name_or_version)
469
- Git.branch?(version.to_s) or raise Error, "Can't find feature #{version}"
470
- project.include_feature(version)
471
- mesg "Included feature '#{name_or_version}'"
472
- mesg "Please resolve eventual conflicts and then commit"
473
- end
474
-
475
- def upgrade
476
- # TODO: Shutdown connections
477
- project.database.version != project.version or raise Error, "Database already up to date"
478
- project.backup
479
- begin
480
- project.upgrade
481
- rescue RuntimeError
482
- project.restore
483
- raise Fail, "Failed upgrading database, rolled back to last version"
484
- end
485
- end
486
-
487
- def backup(file = nil) project.backup(file) end
488
-
489
- def restore(file = nil)
490
- file.nil? || File.exist?(file) or raise Error, "Can't find #{file}"
491
- project.restore(file)
492
- end
493
-
494
- private
495
- def compute_version(version, arg)
496
- if arg.nil?
497
- nil
498
- elsif %w(major minor patch).include?(arg)
499
- version.increment(arg.to_sym)
500
- else
501
- Prick::Version.new(arg)
502
- end
503
- end
504
- end
505
- end
506
-
data/lib/prick/project.rb CHANGED
@@ -92,7 +92,7 @@ module Prick
92
92
  begin
93
93
  branch = Head.load(name)
94
94
  rescue Version::FormatError
95
- raise Fail, "Illegal branch name: #{name}"
95
+ branch = Prick::UserBranch.new(name)
96
96
  end
97
97
  state = ProjectState.new.read
98
98
  Project.new(state.name, state.user, branch)
@@ -222,12 +222,12 @@ module Prick
222
222
  end
223
223
 
224
224
  def generate_schema
225
- build = SchemaBuilder.new(database, SCHEMA_DIR).build(execute: false)
225
+ build = SchemaBuilder.new(database, state_file, SCHEMA_DIR).build(execute: false)
226
226
  puts build.lines
227
227
  end
228
228
 
229
229
  def generate_migration
230
- build = MigrationBuilder.new(database, MIGRATION_DIR).build(execute: false)
230
+ build = MigrationBuilder.new(database, state_file, MIGRATION_DIR).build(execute: false)
231
231
  puts build.lines
232
232
  end
233
233
 
data/lib/prick/version.rb CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Required by gem
6
6
  module Prick
7
- VERSION = "0.15.0"
7
+ VERSION = "0.19.0"
8
8
  end
9
9
 
10
10
  # Project related code starts here
@@ -95,7 +95,7 @@ module Prick
95
95
  # Parse a branch or tag name into a Version object. Return a [version, tag]
96
96
  # tuple where tag is true if name was a tag
97
97
  def self.parse(name)
98
- name =~ VERSION_RE or raise Version::FormatError, "Expected a version, got #{version.inspect}"
98
+ name =~ VERSION_RE or raise Version::FormatError, "Expected a version, got #{name.inspect}"
99
99
  fork, tag, semver, feature = $1, $2, $3, $4
100
100
  version = Version.new(semver, fork: fork, feature: feature)
101
101
  [version, tag]
data/lib/prick.rb CHANGED
@@ -27,5 +27,7 @@ require "prick/version.rb"
27
27
  require "ext/fileutils.rb"
28
28
  require "ext/shortest_path.rb"
29
29
 
30
+ $verbose = 0 if $verbose.nil?
31
+
30
32
  module Prick
31
33
  end
data/prick.gemspec CHANGED
@@ -34,4 +34,20 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "shellopts", "2.0.0.pre.14"
35
35
  spec.add_dependency "semantic"
36
36
  spec.add_dependency "indented_io"
37
+
38
+ # To be able to call fox in development mode (TODO: Remove)
39
+ spec.add_dependency "boolean"
40
+ spec.add_dependency "constrain"
41
+ spec.add_dependency "developer_exceptions"
42
+ spec.add_dependency "pg"
43
+ spec.add_dependency "dry-inflector"
44
+
45
+ # In development mode override load paths for gems whose source are located
46
+ # as siblings of this project directory
47
+ if File.directory?("#{__dir__}/.git")
48
+ local_projects = Dir["../*"].select { |path|
49
+ File.directory?(path) && File.exist?("#{path}/Gemfile")
50
+ }.map { |relpath| "#{File.absolute_path(relpath)}/lib" }
51
+ $LOAD_PATH.unshift *local_projects
52
+ end
37
53
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-19 00:00:00.000000000 Z
11
+ date: 2021-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: shellopts
@@ -52,6 +52,76 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: boolean
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: constrain
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: developer_exceptions
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pg
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: dry-inflector
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
55
125
  description: A release control and management system for postgresql
56
126
  email:
57
127
  - claus.l.rasmussen@gmail.com