prick 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '022876c913aadf3a2b42646f514f51c2db481c32db42a5903f2dbce88303d423'
4
- data.tar.gz: 1772f43a5a99fe93b97e748488f3c5bfa93fddce768176744ebb27e983cbb475
3
+ metadata.gz: ec055b6544046998736dddf822f2ff0563c6e16deeae039f586ec96651ce5b65
4
+ data.tar.gz: 2687ec86827fc0c808a916462b34f7b71ab993fe0d7d5ef5444d4f5775ce91e2
5
5
  SHA512:
6
- metadata.gz: d8c647cc168aee7f0aab47d39d6ce1050331a06b6aa7a04fc8173152618432c377eb7ce42784eb95d2a70134be10bddd0ff3a43298c2e20af8a3649dc8227451
7
- data.tar.gz: 84068d08a68d2b1177054faa4d31218bf7bc66cf437bf255a6ff2404c0126e10288e58519e11fe7c80029c7169ff9e3a59be5327c39f8fd112bc0a846519d833
6
+ metadata.gz: c278a8a151aefeb23c40dc2468fd271e14b45a031f73942453157b3e8789209fa55094bde9d92276fa85c530c204795e8474bd688ccfca139b7945501f373e73
7
+ data.tar.gz: 4d2df4c44b4c10e13d89ee584e5c1839b963d5c7bff42f5618c20b66173f540383cf1bc3059670a5af4922c4ad594a5ceaddc617cdbf2f54a60f2c8a96ce4653
data/TODO CHANGED
@@ -1,3 +1,4 @@
1
+ o eval vs exit
1
2
  o Make it possible to define (nested) environments
2
3
  o Use standard directory layout
3
4
 
data/exe/prick CHANGED
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- #$LOAD_PATH.unshift "/home/clr/prj/shellopts/lib"
4
- #$LOAD_PATH.unshift "/home/clr/prj/pg_graph/lib"
5
- #$LOAD_PATH.unshift "/home/clr/prj/fixture_fox/lib"
6
-
7
3
  require 'bootsnap'
8
4
  begin
9
5
  cache_dir = "/run/user/" + `id -u`.chomp
@@ -38,10 +34,19 @@ SPEC = %(
38
34
  Change to directory DIR before doing anything else
39
35
 
40
36
  -d,database=DATABASE
41
- Override database name from prick.yml
37
+ Override database name. Default is read from .prick.context
42
38
 
43
39
  -U,username=USERNAME
44
- Override username from from prick.yml
40
+ Override username. Default is read from .prick.context
41
+
42
+ -e,environment=ENVIRONMENT
43
+ Override environment. Default is read from from .prick.context
44
+
45
+ -p,project=PRICK-FILE
46
+ Override default environment file ('prick.yml')
47
+
48
+ -c,context=CONTEXT-FILE
49
+ Override default context file ('.prick.context')
45
50
 
46
51
  version!
47
52
  Print project version
@@ -62,6 +67,9 @@ SPEC = %(
62
67
  teardown!
63
68
  Drop the database and the database user. TODO: Also run teardown scripts
64
69
 
70
+ clean!
71
+ Empties the database and drops related users except the database user
72
+
65
73
  create.data!
66
74
  create.schema!
67
75
  create.database!
@@ -79,8 +87,9 @@ SPEC = %(
79
87
  drop! -- [KIND]
80
88
  @ Drop objects
81
89
 
82
- Kind can be 'users', 'data', 'schema', 'database' (the default), or 'all'. It is
83
- not an error if the object doesn't exist. TODO Only 'users' is currently defined
90
+ Kind can be 'users', 'data', 'schema', 'user', 'database' (the default),
91
+ or 'all'. It is not an error if the object doesn't exist. TODO 'data' and
92
+ 'schema' is not implemented
84
93
 
85
94
  build! -f,force -t,time --dump=KIND? -- [SCHEMA]
86
95
  Build the project. If SCHEMA is defined, later schemas are excluded.
@@ -98,7 +107,10 @@ SPEC = %(
98
107
 
99
108
  fox! -- FILE...
100
109
  Load fox file data. Data are reset to their initial state after build
101
- before the fox data are loaded
110
+ before the fox data are loaded. This makes it possible to experiment with
111
+ different data sets
112
+
113
+ # TODO: A --clean option that resets data
102
114
 
103
115
  release! -- KIND
104
116
  Create a release of the given kind. KIND can be 'major', 'minor', or
@@ -112,6 +124,7 @@ SPEC = %(
112
124
  dump.data!
113
125
  dump.schema!
114
126
  dump.database!
127
+ dump.environment!
115
128
  TODO
116
129
 
117
130
  dump.migration! --force -- VERSION
@@ -154,13 +167,18 @@ begin
154
167
  # Check state
155
168
  File.exist?(PRICK_PROJECT_FILE) or raise Prick::Error, "Not in a prick project directory"
156
169
 
170
+ # Handle -p and -c
171
+ project_file = opts.project || PRICK_PROJECT_PATH
172
+ context_file = opts.context || PRICK_CONTEXT_PATH
173
+
157
174
  # Load state
158
- Prick.state = State.load
175
+ state = Prick.state = State.load(project_file, context_file)
176
+
177
+ # Set database, username and environment
178
+ database = state.database = opts.database || state.database
179
+ username = state.username = opts.username || state.username
180
+ environment = state.environment = opts.environment || state.environment
159
181
 
160
- # Handle -d and -U options
161
- database = opts.database || Prick.state.database
162
- username = opts.username || Prick.state.username
163
-
164
182
  # Expect a sub-command
165
183
  cmd = opts.subcommand! or raise Prick::Error, "Subcomand expected"
166
184
 
@@ -180,6 +198,9 @@ begin
180
198
  when :teardown!
181
199
  Prick::SubCommand.teardown(database, username)
182
200
 
201
+ when :clean!
202
+ Prick::SubCommand.clean(database)
203
+
183
204
  when :create!
184
205
  create_command = opts.create!
185
206
  case create_command.subcommand
@@ -190,14 +211,15 @@ begin
190
211
  username, version,
191
212
  force: create_command.subcommand!.force?,
192
213
  file: create_command.subcommand!.file)
193
- else
194
- raise NotImplementedError
214
+ else
215
+ raise NotImplementedError
195
216
  end
196
217
 
197
218
  when :build!
198
219
  dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
220
+ # exclude = cmd.exclude? ? cmd.exclude.split(",") : []
199
221
  Prick::SubCommand.build(
200
- database, username, args.expect(0..1), force: cmd.force?, timer: cmd.time?, dump: dump)
222
+ database, username, args.expect(0..1), force: cmd.force?, timer: cmd.time?, dump: dump)
201
223
 
202
224
  when :make!
203
225
  dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
@@ -212,6 +234,8 @@ begin
212
234
  Prick::SubCommand.drop_all(database)
213
235
  when :users
214
236
  Prick::SubCommand.drop_users(database)
237
+ when :user
238
+ Prick::SubCommand.drop_user(username)
215
239
  when :database
216
240
  Prick::SubCommand.drop_database(database)
217
241
  when :data, :schema
@@ -233,20 +257,44 @@ begin
233
257
  when :dump!
234
258
  subject = cmd.subcommand!
235
259
  case cmd.subcommand
236
- when :migration
260
+ when :migration!
237
261
  arg = args.expect(1)
238
262
  version = PrickVersion.try(arg) or raise "Illegal version number: #{arg}"
239
263
  Prick::SubCommand.create_migration(username, version, force: subject.force?, file: "/dev/stdout")
240
- when :data, :schema, :database
264
+ when :type!
265
+ conn = PgConn.new(database, username)
266
+ builder = Prick::Build::Builder.new(conn, Prick::SCHEMA_DIR)
267
+ meta = PgMeta.new(conn, exclude_schemas: builder.pg_graph_ignore_schemas)
268
+ graph = PgGraph::Type.new(meta, builder.reflections_file)
269
+ graph.dump
270
+ when :data!, :schema!, :database!
241
271
  raise NotImplementedError
272
+ when :environment!
273
+ puts state.bash_source
242
274
  else
243
- raise Prick::Error, "Unknown subject: #{subject.inspect}"
275
+ raise Prick::Error, "Unknown dump object: #{cmd.subcommand!.__name__}"
244
276
  end
245
277
 
278
+
246
279
  else
247
280
  raise Prick::Fail, "Internal error: Unhandled command - #{opts.subcommand.inspect}"
248
281
  end
249
282
 
283
+ rescue Prick::Build::PostgresError => ex
284
+ $stderr.puts "Error: #{ex.message}"
285
+ if STDERR.tty?
286
+ file, lineno, charno = ex.err
287
+ if file && lineno
288
+ lines = IO.readlines(file).map(&:chomp)
289
+ i = lineno - 1
290
+ from = [i - 10, 0].max
291
+ to = [i + 10, lines.size].min
292
+ $stderr.puts
293
+ $stderr.puts lines[from...i], lines[i].bold, lines[i+1...to]
294
+ end
295
+ end
296
+ exit 1
297
+
250
298
  rescue RuntimeError, IOError, ShellOpts::Failure, Prick::Fail, Prick::Build::PostgresError => ex
251
299
  ShellOpts.failure(ex.message)
252
300
 
data/lib/builder/batch.rb CHANGED
@@ -14,10 +14,10 @@ module Prick
14
14
  @nodes = []
15
15
  end
16
16
 
17
- def execute(&block)
17
+ def execute(**opts, &block)
18
18
  name = self.class.to_s.sub(/.*::/, "").split(/(?=[A-Z])/).map(&:downcase).join(" ")
19
19
  time "Execute #{name} (nodes: #{nodes.size})" do
20
- yield
20
+ yield(**opts)
21
21
  end
22
22
  end
23
23
 
@@ -28,23 +28,38 @@ module Prick
28
28
  end
29
29
 
30
30
  class SqlBatch < BuildBatch
31
- def execute
31
+ def execute(step: true) # This is marginally faster!
32
32
  super {
33
33
  begin
34
34
  sql = []
35
- # A SQL batch allows the first node to be an executable
36
- if nodes.first.is_a?(ExeNode)
37
- time "Execute script" do
38
- sql = [nodes.first.source]
35
+ file = nil
36
+ node = nil
37
+
38
+ # A SQL batch allows the first node to be an evaluated or executed node
39
+ if nodes.first.is_a?(CommandNode)
40
+ node = nodes.shift
41
+ sql = [node.source]
42
+ end
43
+
44
+ if step
45
+ conn.execute sql, silent: true if !sql.empty?
46
+ for node in nodes
47
+ conn.execute node.source, silent: true
39
48
  end
49
+ else
50
+ node = nil
51
+ conn.execute sql + nodes[sql.size..-1].map(&:source), silent: true
40
52
  end
41
- conn.execute sql + nodes[sql.size..-1].map(&:source)
42
53
 
43
- rescue PG::SyntaxError, PG::Error => ex
44
- error, line, pos = parse_pg_message(ex.message)
45
- if line
54
+ rescue PG::Error => ex
55
+ error, line, char = conn.err
56
+ file = nil
57
+ if step
58
+ ;
59
+ elsif line
60
+ # Locate error node and make line number relative
46
61
  if line < nodes.first.lines
47
- node = nodes.first;
62
+ node = nodes.first
48
63
  else
49
64
  line -= nodes.first.lines
50
65
  node = nodes[1..-1].find { |node|
@@ -58,26 +73,20 @@ module Prick
58
73
  end
59
74
  }
60
75
  end
76
+ # file = node.path
77
+ end
61
78
 
62
- if node.is_a?(SqlNode)
63
- message = ["prick: #{error} in #{node.path}", line, pos].compact.join(":")
64
- else
65
- message = "prick: #{error} from #{node.path}"
66
- end
79
+ if node.is_a?(SqlNode)
80
+ message = ["#{error} in #{node.path}", line, char].compact.join(":")
81
+ elsif node
82
+ message = "#{error} from #{node.path}"
67
83
  else
68
- raise
84
+ message = conn.errmsg + " in SQL batch"
69
85
  end
70
- raise PostgresError.new(message)
86
+ raise PostgresError.new(message, file, line, char)
71
87
  end
72
88
  }
73
89
  end
74
-
75
- private
76
- # Returns [error, line, pos] tuple
77
- def parse_pg_message(message)
78
- message =~ /ERROR:\s*(.*?)\n(LINE (\d+): ).*?\n(\s*\^)/m or return nil
79
- [$1, $3.to_i, $4.size - $2.size]
80
- end
81
90
  end
82
91
 
83
92
  class ModuleBatch < BuildBatch
@@ -139,7 +148,7 @@ module Prick
139
148
  # FIXME: Why only in FoxBatch - should be set higher up in the system
140
149
  conn.execute "update prick.versions set built_at = now() at time zone 'UTC'"
141
150
  rescue PG::SyntaxError, PG::Error => ex
142
- raise PostgresError, "prick: #{ex.message}"
151
+ raise PostgresError.new(ex.message)
143
152
  end
144
153
  }
145
154
  t_type.emit
@@ -10,7 +10,18 @@ include Constrain
10
10
  module Prick
11
11
  module Build
12
12
  class Error < StandardError; end
13
- class PostgresError < Error; end
13
+ class PostgresError < Error
14
+ attr_reader :file
15
+ attr_reader :lineno
16
+ attr_reader :charno
17
+
18
+ def initialize(message, file = nil, lineno = nil, charno = nil)
19
+ super(message)
20
+ @file, @lineno, @charno = file, lineno, charno
21
+ end
22
+
23
+ def err = [@file, @lineno, @charno]
24
+ end
14
25
 
15
26
  class Builder
16
27
  # PgConn object
data/lib/builder/node.rb CHANGED
@@ -41,7 +41,7 @@ module Prick
41
41
  @source_lines = nil
42
42
  end
43
43
 
44
- def to_s() [path, args].compact.join(" ") end
44
+ def to_s() @to_s ||= [path, args].compact.join(" ") end
45
45
  def inspect() to_s end
46
46
  def dump() puts "#{inspect} (#{schema})" end
47
47
 
@@ -121,41 +121,45 @@ module Prick
121
121
  def self.objects() @@objects end
122
122
  end
123
123
 
124
- class ExeNode < Node
125
- # Using a pipe instead of just executing the command shaves off some
126
- # deciseconds spent starting up bash. It expects the process to read
127
- # database/username from standard input
128
- attr_reader :pipe
124
+ class CommandNode < Node
125
+ alias_method :command, :to_s
126
+
127
+ def filename = File.basename(path)
128
+ def relpath = path.sub(/^#{Dir.getwd}\//, "")
129
129
 
130
130
  def initialize(parent, phase, path, args = nil)
131
+ constrain args, [String]
131
132
  super(parent, phase, :exe, path, args)
132
- @pipe = Command::Pipe.new(to_s, stderr: nil)
133
133
  end
134
134
 
135
135
  def inspect() "#{path} #{(args || []).join(" ")}" end
136
136
 
137
137
  protected
138
- def read_source
138
+ def execute_command
139
139
  begin
140
- pipe.puts [conn.name, conn.user]
141
- sql = pipe.wait
140
+ Command.command(Prick.state.bash_environment, command)
142
141
  rescue Command::Error => ex
143
- message = "prick: Error executing '#{to_s}'\n" + ex.stderr.map { |l| " #{l}" }.join("\n")
142
+ message = "Error executing '#{command}'\n" + ex.stderr.map { |l| " #{l}" }.join("\n")
144
143
  raise Prick::Error.new(message)
145
-
146
- rescue Errno::EPIPE
147
- raise Prick::Error, "Pipe fail in #{path} called from #{parent.path}\n" +
148
- "Tip: Read username/database from standard input"
149
- end
150
- if pipe.status != 0
151
- $stderr.puts pipe.error
152
- raise Prick::Error, "Failed executing #{path} called from #{parent.path}"
153
144
  end
145
+ end
146
+ end
147
+
148
+ class EvalNode < CommandNode
149
+ def read_source
150
+ sql = execute_command
154
151
  @source_lines = 1 + 1 + sql.count("\n")
155
152
  ["set search_path to #{schema}, pg_temp;\n", sql]
156
153
  end
157
154
  end
158
155
 
156
+ class ExecNode < CommandNode
157
+ def read_source
158
+ execute_command
159
+ []
160
+ end
161
+ end
162
+
159
163
  # A build.yml file node
160
164
  class BuildNode < Node
161
165
  def nodes() @nodes ||= init_nodes + decl_nodes + seed_nodes + term_nodes end
@@ -34,6 +34,48 @@ module Prick
34
34
  end
35
35
  end
36
36
 
37
+ # Search for an executable in path. Return nil if not found
38
+ def find_executable(filename) # ChatGPT
39
+ Prick.state.executable_search_path.split(File::PATH_SEPARATOR).each do |directory|
40
+ path = File.join(directory, filename)
41
+ return path if File.executable?(path)
42
+ end
43
+ nil
44
+ end
45
+
46
+ # Expand environments variables file name. Iterates through inherited
47
+ # environments if the file name contain the $ENVIRONMENT variable. Return
48
+ # nil if not found
49
+ #
50
+ # The hierarchy of environments is defined in the PRICK_ENVIRONMENT_FILE
51
+ #
52
+ def expand_filename(dir, filename)
53
+ environment = Prick.state.environment
54
+ bash_vars = Prick.state.bash_environment
55
+
56
+ last = nil
57
+ for env in [environment] + Environment[environment].ancestors.reverse
58
+ bash_vars["ENVIRONMENT"] = env
59
+ file = expand_variables(filename, bash_vars)
60
+ last ||= (file != last and file) or return nil # return if no ENVIRONMENT substitution
61
+ path = (file.start_with?("/") ? file : File.join(dir, file))
62
+
63
+ # Check for file (may be executable)
64
+ return path if File.exist?(path)
65
+
66
+ # Check for executable in search path
67
+ path = find_executable(file) and return path if file !~ /\//
68
+ end
69
+
70
+ # Return nil if not found
71
+ return nil
72
+ end
73
+
74
+ # Expand $ENVIRONMENT variable
75
+ def expand_string(string)
76
+ expand_variables(string, Prick.state.bash_environment)
77
+ end
78
+
37
79
  def parse_directory(parent, dir)
38
80
  build_file = "#{dir}/build.yml".sub(/\/\//, "/")
39
81
  if File.exist? build_file
@@ -77,89 +119,25 @@ module Prick
77
119
  unit
78
120
  end
79
121
 
80
- # Expand $ENVIRONMENT variable in file names. The function implements a
81
- # hierarchy of environments:
82
- #
83
- # production
84
- # (online)
85
- # development
86
- # online
87
- # offline
88
- # backend
89
- # frontend
90
- # test
91
- #
92
- # If an environment doesn't exist in #dir, then the higher level
93
- # environments are tried in turn. Eg. if the current environment is
94
- # 'test', the algorithm expands '$ENVIRONMENT' to 'test', 'offline', and
95
- # 'development' until an existing file is found. If no file is found the
96
- # the unexpanded value is returned
97
- #
98
- # NOTE: This is a hardcoded feature for an internal project. It is
99
- # configurable in the next major version
100
- #
101
- def expand_filename(dir, filename)
102
- if File.exist?("#{dir}/#{filename}")
103
- return filename
104
- elsif filename =~ /\$ENVIRONMENT|\$\{ENVIRONMENT\}/
105
- env = Prick.state.environment.to_s
106
- while true
107
- file = expand_variables(filename, ENVIRONMENT: env, PWD: Dir.getwd)
108
- return file if File.exist? "#{dir}/#{file}"
109
- case env
110
- when "production"
111
- file = expand_variables(filename, ENVIRONMENT: "online", PWD: Dir.getwd) or return nil
112
- return file if File.exist? "#{dir}/#{file}"
113
- return nil
114
- when "development"; return nil
115
- when "online"; env = "development"
116
- when "offline"; env = "development"
117
- when "backend"; env = "offline"
118
- when "frontend"; env = "offline"
119
- when "test"; env = "offline"
120
- else
121
- raise Error, "Illegal env: '#{env}'"
122
- end
123
- end
124
- else
125
- return nil
126
- end
127
- end
128
-
129
- # Expand $ENVIRONMENT variable
130
- def expand_string(string)
131
- expand_variables(string, ENVIRONMENT: Prick.state.environment.to_s, PWD: Dir.getwd)
132
- end
133
-
122
+ # Returns path, filename, and an array of arguments. It is an error if
123
+ # the file can't be found unless #optional is true. In that case a nil
124
+ # value is returned
134
125
  def parse_file_entry(unit, dir, entry)
135
126
  entry = entry.sub(/\/$/, "")
136
127
  if entry =~ /^(\S+?)(\?)?(?:\s+(.+))?\s*$/
137
- filename = $1
128
+ command = $1
138
129
  optional = !$2.nil?
139
- args = expand_string($3 || '').split
140
- file = expand_filename(dir, filename)
141
- file || optional or raise Error, "Can't find #{filename} in #{dir} from #{unit}"
142
- !file.nil? or return nil
130
+ rest = $3
131
+ args = expand_string(rest || '').split
132
+ path = expand_filename(dir, command)
133
+ path || optional or raise Error, "Can't find '#{entry}' in #{dir}/ from #{unit}"
134
+ !path.nil? or return nil
143
135
  else
144
136
  raise Error, "Not a file name: '#{entry}'"
145
137
  end
146
- path = "#{dir}/#{file}"
147
- [path, entry, file, args].flatten
138
+ [path, Array(args).flatten]
148
139
  end
149
140
 
150
- # def parse_file_entry(dir, entry)
151
- # name = expand_environment(dir, entry)
152
- # name.sub!(/\/$/, "")
153
- # if name =~ /^(\S+)\s+(.+)$/ # has arguments -> exe
154
- # file = $1
155
- # args = $2.split
156
- # else
157
- # file = name
158
- # end
159
- # path = "#{dir}/#{file}"
160
- # [path, name, file, args]
161
- # end
162
-
163
141
  def parse_entry(unit, phase, dir, entry)
164
142
  if entry.is_a?(Hash)
165
143
  entry.size == 1 or raise Error, "sql and module are single-line values"
@@ -168,34 +146,40 @@ module Prick
168
146
  when "sql"
169
147
  InlineNode.new(unit, phase, unit.path, expand_string(value))
170
148
  when "call"
171
- (path, name, file, klass, command, args = parse_file_entry(unit, dir, value)) or return nil
149
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
150
+ args.size >= 2 or raise Error, "Illegal number of arguments"
151
+ klass = args.shift
152
+ command = args.shift
172
153
  klass && command or raise "Illegal number of arguments: #{value}"
173
- ModuleNode.new(unit, phase, "#{dir}/#{file}", klass, command, args)
154
+ ModuleNode.new(unit, phase, path, klass, command, args)
155
+ when "eval"
156
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
157
+ EvalNode.new(unit, phase, path, args)
174
158
  when "exec"
175
- (path, name, file, args = parse_file_entry(unit, dir, value)) or return nil
176
- ExeNode.new(unit, phase, path, args)
159
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
160
+ ExecNode.new(unit, phase, path, args)
177
161
  else
178
162
  raise Error, "Illegal key: #{key}"
179
163
  end
180
164
  else
181
- (path, name, file, args = parse_file_entry(unit, dir, entry)) or return nil
165
+ (path, args = parse_file_entry(unit, dir, entry)) or return nil
182
166
  if File.directory? path
183
167
  parse_directory(unit, path)
168
+ elsif File.executable? path
169
+ ExecNode.new(unit, phase, path, args)
184
170
  elsif File.file? path
185
171
  case path
186
172
  when /\.sql$/
187
173
  SqlNode.new(unit, phase, path)
188
174
  when /\.fox$/
189
175
  FoxNode.new(unit, :seed, path)
176
+ when /build-.*\.yml$/
177
+ parse_build_file(unit, dir, path)
190
178
  else
191
- if File.executable? path
192
- ExeNode.new(unit, phase, path, args)
193
- else
194
- raise Error, "Unrecognized file type #{File.basename(path)} in #{dir}"
195
- end
179
+ raise Error, "Expected executable, fox, or sql file: #{File.basename(path)} in #{dir}"
196
180
  end
197
181
  else
198
- raise Error, "Can't find #{name} in #{dir} from #{unit}"
182
+ raise Error, "Can't find '#{entry}' in #{dir}/ from #{unit}"
199
183
  end
200
184
  end
201
185
  end
@@ -6,7 +6,7 @@
6
6
  # backslash. All occurrences of a variable in the string are replaced
7
7
  #
8
8
  # The str argument is the template string and the variables argument is a hash
9
- # from variable name (Symbol) to variable value
9
+ # from variable name (String) to variable value
10
10
  #
11
11
  # Note that the characters '\x00' and '\x01' are used internally and may not be
12
12
  # present in the template string
@@ -17,11 +17,24 @@ def expand_variables(str, variables) # ChatGPT
17
17
 
18
18
  # Expand variables
19
19
  str.gsub!(/\$(\w+)\b|\$\{(\w+)\}/) do |match| # Strange that '\b' is necessary
20
- key = ($1 || $2).to_sym
21
- variables[key] || match
20
+ key = $1 || $2
21
+ # variables[key] || match
22
+ variables[key] || ""
22
23
  end
23
24
 
24
25
  # Restore escaped characters
25
26
  str.gsub("\x00", '\\').gsub("\x01", '$')
26
27
  end
27
28
 
29
+ # Return true if any of the variables will be expanded
30
+ def expand_variables?(str, variables)
31
+ # Replace escaped bashslashes and dollar signs
32
+ str = str.gsub('\\\\', "\x00").gsub('\\$', "\x01")
33
+
34
+ # Look for expansion
35
+ str.gsub!(/\$(\w+)\b|\$\{(\w+)\}/) do |match| # Strange that '\b' is necessary
36
+ return true if variables.include?($1 || $2)
37
+ end
38
+
39
+ return false
40
+ end
data/lib/local/ansi.rb ADDED
@@ -0,0 +1,6 @@
1
+
2
+ class String
3
+ ANSI_BOLD_START = "\e[1m"
4
+ ANSI_BOLD_STOP = "\e[22m"
5
+ def bold = "#{ANSI_BOLD_START}#{self.to_s}#{ANSI_BOLD_STOP}"
6
+ end
data/lib/local/command.rb CHANGED
@@ -97,16 +97,15 @@ module Command
97
97
  # command on standard-input, if it is a IO object that IO object is piped to
98
98
  # the command
99
99
  #
100
- # By default #command pass through standard-error but if :stderr is true,
101
- # #command will return a tuple of standard-output, standard-error lines. If
102
- # :stderr is false, standard-error is ignored and is the same as adding
103
- # "2>/dev/null" to the command
100
+ # By default #command pass through stderr but if :stderr is true, #command
101
+ # will instead return a tuple of stdout/stderr lines. If :stderr is false,
102
+ # stderr is ignored and is the same as adding "2>/dev/null" to the command
104
103
  #
105
- # #command raises a Command::Error exception if the command return with an
106
- # exit code != 0 unless :fail is false. In that case the the exit code can be
104
+ # #command raises a Command::Error exception if the command returns with an exit
105
+ # code != 0 unless :fail is false. In that case the the exit code can be
107
106
  # fetched from Command::status
108
107
  #
109
- def command(cmd, stdin: nil, stderr: nil, fail: true)
108
+ def command(env = {}, cmd, stdin: nil, stderr: nil, fail: true)
110
109
  cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
111
110
 
112
111
  pw = IO::pipe # pipe[0] for read, pipe[1] for write
@@ -129,7 +128,8 @@ module Command
129
128
  STDERR.reopen(pe[1])
130
129
  pe[1].close
131
130
 
132
- exec(cmd)
131
+ env
132
+ exec(env.map { |k,v| [k.to_s, v.to_s] }.to_h, cmd)
133
133
  }
134
134
 
135
135
  pw[0].close
@@ -143,15 +143,14 @@ module Command
143
143
  when Array; pw[1].write(stdin.join("\n") + "\n")
144
144
  end
145
145
  pw[1].flush
146
- pw[1].close
147
146
  end
147
+ pw[1].close # Closing standard input so the command doesn't hang on read
148
148
 
149
149
  @status = Process.waitpid2(pid)[1].exitstatus
150
150
 
151
151
  out = pr[0].readlines.map(&:chomp)
152
152
  err = pe[0].readlines.map(&:chomp)
153
153
 
154
- pw[1].close if !stdin
155
154
  pr[0].close
156
155
  pe[0].close
157
156
 
@@ -171,13 +170,6 @@ module Command
171
170
  end
172
171
  end
173
172
 
174
- # Exit status of the last command. FIXME: This is not usable because the
175
- # methods raise an exception if the command exited with anything but 0
176
- def status() @status end
177
-
178
- # Stored exception when #command is called with :fail true
179
- def exception() @exception end
180
-
181
173
  # Like command but returns true if the command exited with the expected
182
174
  # status. Note that it suppresses standard-error by default
183
175
  #
@@ -186,6 +178,12 @@ module Command
186
178
  @status == expect
187
179
  end
188
180
 
181
+ # Exit status of the last command
182
+ def status() @status end
183
+
184
+ # Exception of the last command if it failed, otherwise nil TODO What is this used for?
185
+ def exception() @exception end
186
+
189
187
  module_function :command
190
188
  module_function :status
191
189
  module_function :exception
@@ -14,8 +14,10 @@ module Prick
14
14
  DIRS = [
15
15
  MIGRATION_DIR = "migration",
16
16
  SCHEMA_DIR = "schema",
17
- PRICK_DIR = "#{SCHEMA_DIR}/prick",
17
+ SCHEMA_PRICK_DIR = "#{SCHEMA_DIR}/prick",
18
18
  PUBLIC_DIR = "#{SCHEMA_DIR}/public",
19
+ BIN_DIR = "bin",
20
+ LIBEXEC_DIR = "libexec",
19
21
  VAR_DIR = "var",
20
22
  CACHE_DIR = "#{VAR_DIR}/cache",
21
23
  SPOOL_DIR = "#{VAR_DIR}/spool",
@@ -24,10 +26,20 @@ module Prick
24
26
  SPEC_DIR = "spec"
25
27
  ]
26
28
 
29
+ # Prick project root directory
30
+ PRICK_DIR = Dir.getwd
31
+
32
+ # Search path for executables
33
+ PRICK_PATH = "#{ENV['PATH']}:#{PRICK_DIR}/#{BIN_DIR}:#{PRICK_DIR}/#{LIBEXEC_DIR}"
34
+
27
35
  # Project specification file
28
36
  PRICK_PROJECT_FILE = "prick.yml"
29
37
  PRICK_PROJECT_PATH = PRICK_PROJECT_FILE
30
38
 
39
+ # # Environment specification file
40
+ # PRICK_ENVIRONMENT_FILE = "prick.environment.yml"
41
+ # PRICK_ENVIRONMENTS_PATH = PRICK_ENVIRONMENT_FILE
42
+
31
43
  # Context file
32
44
  PRICK_CONTEXT_FILE = ".prick-context"
33
45
  PRICK_CONTEXT_PATH = PRICK_CONTEXT_FILE
@@ -42,7 +54,7 @@ module Prick
42
54
 
43
55
  # Schema data file
44
56
  SCHEMA_VERSION_FILE = "data.sql"
45
- SCHEMA_VERSION_PATH = File.join(PRICK_DIR, SCHEMA_VERSION_FILE)
57
+ SCHEMA_VERSION_PATH = File.join(SCHEMA_PRICK_DIR, SCHEMA_VERSION_FILE)
46
58
 
47
59
  # Rspec temporary directory
48
60
  SPEC_TMP_DIR = "spec"
@@ -0,0 +1,134 @@
1
+ require 'yaml'
2
+ require 'dsort'
3
+
4
+ module Prick
5
+ class Environment
6
+ # Environments name
7
+ attr_reader :name
8
+
9
+ # Names of parent environments
10
+ def parents = @values[:parents]
11
+
12
+ # Ancestors sorted in dependency order
13
+ def ancestors
14
+ @ancestors ||= effective_values[:parents].sort_by { |name| @@SORTED_INDEXES[name] }
15
+ end
16
+
17
+ # List of variables including :parents
18
+ def variables = @@VARIABLES
19
+
20
+ # List of user defined variables
21
+ def user_variables = @@VARIABLES - [:parent]
22
+
23
+ # Hash from variable to value
24
+ attr_accessor :values
25
+
26
+ # Hash from variable name to effective value. The effective value is the
27
+ # sum of this environment's value and its parents' values
28
+ attr_accessor :effective_values
29
+
30
+ def self.[](name) @@ENVIRONMENTS[name] end
31
+ def self.environment?(name) @@ENVIRONMENTS.key?(name) end
32
+ def self.environments() @@SORTED_ENVIRONMENTS.map { |key| @@ENVIRONMENTS[key] } end
33
+ def self.variables() @@VARIABLES end
34
+
35
+ def initialize(name)
36
+ @name = name
37
+ @values = variables.map { |key| [key, []] }.to_h
38
+ @effective_values = variables.map { |key| [key, []] }.to_h
39
+ @@ENVIRONMENTS[name] = self
40
+ end
41
+
42
+ def self.load_environments(hash) # hash can be nil
43
+ if hash
44
+ parse(hash)
45
+ analyze
46
+ end
47
+ end
48
+
49
+ # does not include the name of the environment
50
+ def bash_env
51
+ user_variables.map { |variable|
52
+ ["PRICK_ENVIRONMENT_#{variable.upcase}", effective_values[variable]]
53
+ }.to_h
54
+ end
55
+
56
+ def dump
57
+ puts name
58
+ indent {
59
+ values.each { |var, val|
60
+ next if val.empty?
61
+ puts "#{var}:"
62
+ indent { puts val }
63
+ }
64
+ effective_values.each { |var, val|
65
+ next if val.empty?
66
+ puts "effective_#{var}:"
67
+ indent { puts val }
68
+ }
69
+ }
70
+ end
71
+
72
+ def self.dump
73
+ environments.each(&:dump)
74
+ end
75
+
76
+ private
77
+ @@VARIABLES = []
78
+ @@ENVIRONMENTS = {}
79
+ @@SORTED_ENVIRONMENTS = []
80
+
81
+ def self.yaml_to_array(value)
82
+ !value.nil? && Array(value).flatten.compact.map(&:to_s).map(&:split).flatten
83
+ end
84
+
85
+ def self.parse(hash)
86
+ hash = hash.dup
87
+ @@VARIABLES = [:parents] + (yaml_to_array(hash.delete("variables")).map(&:to_sym) || [])
88
+
89
+ for environment, settings in hash
90
+ env = Environment.new(environment)
91
+ if settings.is_a? Hash
92
+ for variable, value in settings
93
+ variable = (variable == "inherit" ? :parents : variable.to_sym)
94
+ value = yaml_to_array(value)
95
+ if variables.include?(variable)
96
+ env.values[variable] = value
97
+ else
98
+ raise ArgumentError, "Illegal variable: '#{variable}'"
99
+ end
100
+ end
101
+ else
102
+ raise ArgumentError, "Illegal value for '#{environment}': #{settings.inspect}"
103
+ end
104
+ end
105
+ end
106
+
107
+ def self.analyze
108
+ # Sort environments in dependency order
109
+ deps = @@ENVIRONMENTS.map { |name, env| [name, env.parents] }
110
+ @@SORTED_ENVIRONMENTS = DSort.dsort(deps)
111
+ @@SORTED_INDEXES = @@SORTED_ENVIRONMENTS.map.with_index { |env, idx| [env, idx] }.to_h
112
+
113
+ # Check for undeclared inherited environments
114
+ @@SORTED_ENVIRONMENTS.each { |environment|
115
+ @@ENVIRONMENTS.key?(environment) or raise ArgumentError, "Can't find '#{inherited}' environment"
116
+ }
117
+
118
+ # Compute effective attribute values by processing environments in
119
+ # dependency order so that all parents' environments are computed before
120
+ # the current environment
121
+ for name in @@SORTED_ENVIRONMENTS
122
+ env = Environment[name]
123
+ env.effective_values = env.values.transform_values { |v| v.dup } # Deep-dup
124
+ for parent in env.parents.dup
125
+ for var in variables
126
+ env.effective_values[var].unshift *Environment[parent].effective_values[var]
127
+ end
128
+ end
129
+ env.effective_values.transform_values! { |v| v.uniq }
130
+ end
131
+ end
132
+ end
133
+ end
134
+
data/lib/prick/state.rb CHANGED
@@ -27,42 +27,79 @@ module Prick
27
27
  # Version of prick(1)
28
28
  attr_accessor :prick_version
29
29
 
30
- # Run-time environment. Can be :production, :development, or :test
31
- attr_accessor :environment
32
-
33
30
  # Name of database
34
31
  attr_accessor :database
35
32
 
36
33
  # Name of database user
37
34
  attr_accessor :username
38
35
 
39
- def self.load
40
- begin
41
- h = YAML.load(File.read PRICK_PROJECT_PATH)
42
- rescue Errno::ENOENT
43
- raise Prick::Error, "Can't open project file: #{PRICK_PROJECT_PATH}"
44
- end
45
- state = State.new
46
- state.name = h["name"]
47
- state.title = h["title"]
48
- state.version = h["version"] && PrickVersion.new(h["version"])
49
- state.prick_version = h["prick"] && PrickVersion.new(h["prick"])
36
+ # Run-time environment name
37
+ def environment() @environment end
38
+ def environment=(env)
39
+ constrain env, String
40
+ Environment.environment?(env) or raise "Illegal environment: '#{env}'"
41
+ @environment = env
42
+ end
50
43
 
51
- begin
52
- h = YAML.load(File.read PRICK_CONTEXT_PATH)
53
- rescue Errno::ENOENT
54
- raise Prick::Error, "Can't open environment file: #{PRICK_CONTEXT_PATH}"
44
+ # Prick project dir. This is not a constant because exe/prick can change
45
+ # directory (FIXME it can?)
46
+ def prick_dir
47
+ @prick_dir ||= Dir.getwd
48
+ end
49
+
50
+ # Prick executable search_path
51
+ def executable_search_path
52
+ @executable_search_path ||= "#{ENV['PATH']}:#{prick_dir}/#{BIN_DIR}:#{prick_dir}/#{LIBEXEC_DIR}"
53
+ end
54
+
55
+ # Create a bash(1) environment (Hash). It is used for in-prick expansion of
56
+ # variables and also injected into the enviroment of subprocesses
57
+ def bash_environment(scope = :global)
58
+ hash = {
59
+ "DATABASE" => Prick.state.database, # FIXME: Yt
60
+ "USERNAME" => Prick.state.username, # FIXME: Yt
61
+ "ENVIRONMENT" => Prick.state.environment.to_s, # FIXME: Yt
62
+ "PRICK_NAME" => Prick.state.name,
63
+ "PRICK_TITLE" => Prick.state.title,
64
+ "PRICK_VERSION" => Prick.state.version,
65
+ "PRICK_DATABASE" => Prick.state.database,
66
+ "PRICK_USERNAME" => Prick.state.username,
67
+ "PRICK_ENVIRONMENT" => Prick.state.environment.to_s,
68
+ }
69
+ case scope
70
+ when :local; # nop
71
+ hash.merge Environment[Prick.state.environment].bash_env
72
+ when :global;
73
+ ENV.to_h.merge(hash).merge({
74
+ "PATH" => Prick.state.executable_search_path,
75
+ "PRICK_SCHEMADIR" => File.join(Prick.state.prick_dir, SCHEMA_DIR),
76
+ "PRICK_BINDIR" => File.join(Prick.state.prick_dir, BIN_DIR),
77
+ "PRICK_LIBEXECDIR" => File.join(Prick.state.prick_dir, LIBEXEC_DIR),
78
+ "PRICK_VARDIR" => File.join(Prick.state.prick_dir, VAR_DIR),
79
+ "PRICK_CACHEDIR" => File.join(Prick.state.prick_dir, CACHE_DIR),
80
+ "PRICK_SPOOLDIR" => File.join(Prick.state.prick_dir, SPOOL_DIR),
81
+ "PRICK_TMPDIR" => File.join(Prick.state.prick_dir, TMP_DIR),
82
+ "PRICK_CLONEDIR" => File.join(Prick.state.prick_dir, CLONE_DIR),
83
+ "PRICK_SPECDIR" => File.join(Prick.state.prick_dir, BIN_DIR)
84
+ }).merge(Environment[Prick.state.environment].bash_env)
85
+ else
86
+ raise ArgumentError
55
87
  end
56
- state.environment = h["environment"]&.to_sym
57
- state.database = h["database"]
58
- state.username = h["username"]
88
+ end
59
89
 
60
- # TODO Load schema version
90
+ def bash_source
91
+ bash_environment(:local).map { |var,val| "export #{var}=\"#{Array(val).join(' ')}\"\n" }.join
92
+ end
61
93
 
94
+ def self.load(project_file, context_file)
95
+ state = State.new
96
+ state.send(:parse_project_file, project_file || PRICK_PROJECT_PATH)
97
+ state.send(:parse_context_file, context_file || PRICK_CONTEXT_PATH)
62
98
  state
63
99
  end
64
100
 
65
101
  def save
102
+ raise NotImplementedError
66
103
  h = {
67
104
  "name" => name,
68
105
  "title" => title,
@@ -97,11 +134,45 @@ module Prick
97
134
  indent {
98
135
  for method in [
99
136
  :name, :title, :prick_version, :project_version, :schema_version,
100
- :database_version, :environment, :database, :username]
137
+ :database_version, :database, :username]
101
138
  puts "#{method}: #{self.send method}"
102
139
  end
140
+ puts "environments:"
141
+ indent { Environment.dump }
103
142
  }
104
143
  end
144
+
145
+ private
146
+ def load_yaml(file, expected_keys, optional_keys)
147
+ begin
148
+ hash = YAML.load(File.read file)
149
+ rescue Errno::ENOENT
150
+ raise Prick::Error, "Can't read #{file}"
151
+ end
152
+ for key in expected_keys
153
+ !hash[key].to_s.empty? or raise Prick::Error, "Can't find '#{key}' in #{file}"
154
+ end
155
+ (unknown = (hash.keys - expected_keys - optional_keys).first) and
156
+ Prick::Error "Illegal key '#{unknown}' in #{file}"
157
+ hash
158
+ end
159
+
160
+ def parse_context_file(context_file)
161
+ hash = load_yaml(context_file, %w(environment database username), [])
162
+ @database = hash["database"]
163
+ @username = hash["username"]
164
+ @environment = hash["environment"]
165
+ end
166
+
167
+ def parse_project_file(prick_file)
168
+ hash = load_yaml(prick_file, %w(name title), %w(version prick environments))
169
+ @name = hash["name"]
170
+ @title = hash["title"]
171
+ @version = hash["version"] && PrickVersion.new(hash["version"])
172
+ @prick_version = hash["prick"] && PrickVersion.new(hash["prick"])
173
+ Environment.load_environments(hash["environments"] || {})
174
+ end
105
175
  end
106
176
  end
107
177
 
178
+
data/lib/prick/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prick
4
- VERSION = "0.28.0"
4
+ VERSION = "0.29.0"
5
5
  end
data/lib/prick.rb CHANGED
@@ -17,7 +17,9 @@ require 'ext/expand_variables.rb'
17
17
  require 'local/command.rb'
18
18
  require 'local/git.rb'
19
19
  require 'local/timer.rb'
20
+ require 'local/ansi.rb'
20
21
 
22
+ require 'prick/environment.rb'
21
23
  require 'prick/state.rb'
22
24
  require 'prick/prick_version.rb'
23
25
  require 'prick/diff.rb'
@@ -33,6 +35,7 @@ end
33
35
  require 'subcommand/prick-init.rb'
34
36
  require 'subcommand/prick-setup.rb'
35
37
  require 'subcommand/prick-teardown.rb'
38
+ require 'subcommand/prick-clean.rb'
36
39
  require 'subcommand/prick-create.rb'
37
40
  require 'subcommand/prick-build.rb'
38
41
  require 'subcommand/prick-make.rb'
@@ -3,7 +3,10 @@
3
3
  require 'builder/builder.rb'
4
4
 
5
5
  module Prick::SubCommand
6
- def self.build(database, username, schema, builddir: "schema", force: false, timer: nil, dump: nil)
6
+ def self.build(
7
+ database, username, schema,
8
+ builddir: "schema", force: false, timer: nil, dump: nil)
9
+
7
10
  Timer.on! if timer
8
11
  time "Prick::Command#build" do
9
12
  begin
@@ -15,20 +18,19 @@ module Prick::SubCommand
15
18
  builder = nil
16
19
  time "Load build object" do
17
20
  if super_conn.rdbms.exist? database
18
- conn = PgConn.new(database, username)
19
21
  exist = true
20
22
  else
21
23
  super_conn.rdbms.create database, owner: username
22
- conn = PgConn.new(database, username)
23
24
  exist = false
24
25
  end
26
+ conn = PgConn.new(database, username)
25
27
 
26
28
  builder = Prick::Build::Builder.new(conn, builddir)
27
29
 
28
30
  if exist
29
31
  if force
30
32
  # Drop all schemas but re-creates the public schema
31
- super_conn.rdbms.empty!(database)
33
+ super_conn.rdbms.empty!(database, public: false)
32
34
 
33
35
  else
34
36
  # Find schemas to refresh. This includes all schemas in the
@@ -68,9 +70,9 @@ module Prick::SubCommand
68
70
  builder.execute conn
69
71
  end
70
72
 
71
- rescue Prick::Error => ex
72
- $stderr.puts ex.message
73
- exit 1
73
+ # rescue Prick::Error => ex
74
+ # $stderr.puts ex.message
75
+ # exit 1
74
76
 
75
77
  rescue ::Command::Error => ex
76
78
  $stderr.puts ex.message
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'builder/builder.rb'
4
+
5
+ module Prick::SubCommand
6
+ # Drop users and empty database. This has the same as effect as
7
+ # teardown+setup but without recreating the user and the database that
8
+ # annoyingly terminates all user sessions
9
+ def self.clean(database)
10
+ drop_users(database)
11
+ PgConn.new("postgres") { |conn|
12
+ conn.rdbms.empty!(database) if conn.rdbms.exist?(database)
13
+ }
14
+ end
15
+ end
16
+
17
+
@@ -3,8 +3,15 @@
3
3
  require 'builder/builder.rb'
4
4
 
5
5
  module Prick::SubCommand
6
- def self.drop_users(database)
6
+ def self.drop_user(username) # No database so does not cascade
7
7
  PgConn.new("postgres") { |conn|
8
+ conn.role.drop([username])
9
+ }
10
+ end
11
+
12
+ # Drop all users associated with the given database except the owner
13
+ def self.drop_users(database)
14
+ PgConn.new(database) { |conn|
8
15
  users = conn.role.list(database: database)
9
16
  conn.role.drop(users, cascade: true)
10
17
  }
@@ -15,11 +22,9 @@ module Prick::SubCommand
15
22
  end
16
23
 
17
24
  def self.drop_all(database)
18
- PgConn.new("postgres") { |conn|
19
- users = conn.role.list(database: database)
20
- PgConn.new(database) { |db| db.role.drop(users, cascade: true) }
21
- conn.rdbms.drop database
22
- }
25
+ drop_users(database)
26
+ drop_database(database)
27
+ drop_user(username)
23
28
  end
24
29
  end
25
30
 
@@ -0,0 +1,14 @@
1
+ online:
2
+ offline:
3
+ production: online
4
+ development: offline
5
+ frontend: development
6
+ app_registry_frontend: frontend
7
+ app_portal_frontend: frontend
8
+ backend: development
9
+ app_registry_backend: backend
10
+ app_portal_backend: backend
11
+ fdw_import: online
12
+ sagsys_import: offline
13
+ app_portal_import: offline
14
+ test: offline
data/prick.gemspec CHANGED
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency "postspec"
39
39
  spec.add_dependency "pg_graph"
40
40
  spec.add_dependency "shellopts"
41
+ spec.add_dependency "dsort"
41
42
 
42
43
  spec.add_development_dependency "ruby-prof"
43
44
 
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.28.0
4
+ version: 0.29.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: 2024-01-08 00:00:00.000000000 Z
11
+ date: 2024-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: dsort
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: ruby-prof
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -174,12 +188,14 @@ files:
174
188
  - lib/builder/node_pool.rb
175
189
  - lib/builder/parser.rb
176
190
  - lib/ext/expand_variables.rb
191
+ - lib/local/ansi.rb
177
192
  - lib/local/command.rb
178
193
  - lib/local/git.rb
179
194
  - lib/local/timer.rb
180
195
  - lib/prick.rb
181
196
  - lib/prick/constants.rb
182
197
  - lib/prick/diff.rb
198
+ - lib/prick/environment.rb
183
199
  - lib/prick/prick_version.rb
184
200
  - lib/prick/state.rb
185
201
  - lib/prick/version.rb
@@ -203,6 +219,7 @@ files:
203
219
  - lib/share/migrate/migration/diff.before-tables.sql
204
220
  - lib/share/migrate/migration/diff.tables.sql
205
221
  - lib/subcommand/prick-build.rb
222
+ - lib/subcommand/prick-clean.rb
206
223
  - lib/subcommand/prick-create.rb
207
224
  - lib/subcommand/prick-drop.rb
208
225
  - lib/subcommand/prick-fox.rb
@@ -212,6 +229,7 @@ files:
212
229
  - lib/subcommand/prick-release.rb
213
230
  - lib/subcommand/prick-setup.rb
214
231
  - lib/subcommand/prick-teardown.rb
232
+ - prick.environments.yml
215
233
  - prick.gemspec
216
234
  homepage: http://www.nowhere.com/
217
235
  licenses: []
@@ -231,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
249
  - !ruby/object:Gem::Version
232
250
  version: '0'
233
251
  requirements: []
234
- rubygems_version: 3.3.7
252
+ rubygems_version: 3.3.18
235
253
  signing_key:
236
254
  specification_version: 4
237
255
  summary: A release control and management system for postgresql