prick 0.28.1 → 0.29.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/prick +68 -16
- data/lib/builder/batch.rb +34 -25
- data/lib/builder/builder.rb +12 -1
- data/lib/builder/parser.rb +25 -66
- data/lib/ext/expand_variables.rb +16 -3
- data/lib/local/ansi.rb +6 -0
- data/lib/prick/constants.rb +4 -0
- data/lib/prick/environment.rb +134 -0
- data/lib/prick/state.rb +86 -44
- data/lib/prick/version.rb +1 -1
- data/lib/prick.rb +3 -0
- data/lib/subcommand/prick-build.rb +9 -8
- data/lib/subcommand/prick-clean.rb +17 -0
- data/lib/subcommand/prick-drop.rb +11 -6
- data/prick.environments.yml +14 -0
- data/prick.gemspec +1 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec055b6544046998736dddf822f2ff0563c6e16deeae039f586ec96651ce5b65
|
4
|
+
data.tar.gz: 2687ec86827fc0c808a916462b34f7b71ab993fe0d7d5ef5444d4f5775ce91e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c278a8a151aefeb23c40dc2468fd271e14b45a031f73942453157b3e8789209fa55094bde9d92276fa85c530c204795e8474bd688ccfca139b7945501f373e73
|
7
|
+
data.tar.gz: 4d2df4c44b4c10e13d89ee584e5c1839b963d5c7bff42f5618c20b66173f540383cf1bc3059670a5af4922c4ad594a5ceaddc617cdbf2f54a60f2c8a96ce4653
|
data/exe/prick
CHANGED
@@ -34,10 +34,19 @@ SPEC = %(
|
|
34
34
|
Change to directory DIR before doing anything else
|
35
35
|
|
36
36
|
-d,database=DATABASE
|
37
|
-
Override database name from prick.
|
37
|
+
Override database name. Default is read from .prick.context
|
38
38
|
|
39
39
|
-U,username=USERNAME
|
40
|
-
Override username
|
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')
|
41
50
|
|
42
51
|
version!
|
43
52
|
Print project version
|
@@ -58,6 +67,9 @@ SPEC = %(
|
|
58
67
|
teardown!
|
59
68
|
Drop the database and the database user. TODO: Also run teardown scripts
|
60
69
|
|
70
|
+
clean!
|
71
|
+
Empties the database and drops related users except the database user
|
72
|
+
|
61
73
|
create.data!
|
62
74
|
create.schema!
|
63
75
|
create.database!
|
@@ -75,8 +87,9 @@ SPEC = %(
|
|
75
87
|
drop! -- [KIND]
|
76
88
|
@ Drop objects
|
77
89
|
|
78
|
-
Kind can be 'users', 'data', 'schema', 'database' (the default),
|
79
|
-
not an error if the object doesn't exist. TODO
|
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
|
80
93
|
|
81
94
|
build! -f,force -t,time --dump=KIND? -- [SCHEMA]
|
82
95
|
Build the project. If SCHEMA is defined, later schemas are excluded.
|
@@ -94,7 +107,10 @@ SPEC = %(
|
|
94
107
|
|
95
108
|
fox! -- FILE...
|
96
109
|
Load fox file data. Data are reset to their initial state after build
|
97
|
-
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
|
98
114
|
|
99
115
|
release! -- KIND
|
100
116
|
Create a release of the given kind. KIND can be 'major', 'minor', or
|
@@ -108,6 +124,7 @@ SPEC = %(
|
|
108
124
|
dump.data!
|
109
125
|
dump.schema!
|
110
126
|
dump.database!
|
127
|
+
dump.environment!
|
111
128
|
TODO
|
112
129
|
|
113
130
|
dump.migration! --force -- VERSION
|
@@ -150,13 +167,18 @@ begin
|
|
150
167
|
# Check state
|
151
168
|
File.exist?(PRICK_PROJECT_FILE) or raise Prick::Error, "Not in a prick project directory"
|
152
169
|
|
170
|
+
# Handle -p and -c
|
171
|
+
project_file = opts.project || PRICK_PROJECT_PATH
|
172
|
+
context_file = opts.context || PRICK_CONTEXT_PATH
|
173
|
+
|
153
174
|
# Load state
|
154
|
-
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
|
155
181
|
|
156
|
-
# Handle -d and -U options
|
157
|
-
database = opts.database || Prick.state.database
|
158
|
-
username = opts.username || Prick.state.username
|
159
|
-
|
160
182
|
# Expect a sub-command
|
161
183
|
cmd = opts.subcommand! or raise Prick::Error, "Subcomand expected"
|
162
184
|
|
@@ -176,6 +198,9 @@ begin
|
|
176
198
|
when :teardown!
|
177
199
|
Prick::SubCommand.teardown(database, username)
|
178
200
|
|
201
|
+
when :clean!
|
202
|
+
Prick::SubCommand.clean(database)
|
203
|
+
|
179
204
|
when :create!
|
180
205
|
create_command = opts.create!
|
181
206
|
case create_command.subcommand
|
@@ -186,14 +211,15 @@ begin
|
|
186
211
|
username, version,
|
187
212
|
force: create_command.subcommand!.force?,
|
188
213
|
file: create_command.subcommand!.file)
|
189
|
-
|
190
|
-
|
214
|
+
else
|
215
|
+
raise NotImplementedError
|
191
216
|
end
|
192
217
|
|
193
218
|
when :build!
|
194
219
|
dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
|
220
|
+
# exclude = cmd.exclude? ? cmd.exclude.split(",") : []
|
195
221
|
Prick::SubCommand.build(
|
196
|
-
|
222
|
+
database, username, args.expect(0..1), force: cmd.force?, timer: cmd.time?, dump: dump)
|
197
223
|
|
198
224
|
when :make!
|
199
225
|
dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
|
@@ -208,6 +234,8 @@ begin
|
|
208
234
|
Prick::SubCommand.drop_all(database)
|
209
235
|
when :users
|
210
236
|
Prick::SubCommand.drop_users(database)
|
237
|
+
when :user
|
238
|
+
Prick::SubCommand.drop_user(username)
|
211
239
|
when :database
|
212
240
|
Prick::SubCommand.drop_database(database)
|
213
241
|
when :data, :schema
|
@@ -229,20 +257,44 @@ begin
|
|
229
257
|
when :dump!
|
230
258
|
subject = cmd.subcommand!
|
231
259
|
case cmd.subcommand
|
232
|
-
when :migration
|
260
|
+
when :migration!
|
233
261
|
arg = args.expect(1)
|
234
262
|
version = PrickVersion.try(arg) or raise "Illegal version number: #{arg}"
|
235
263
|
Prick::SubCommand.create_migration(username, version, force: subject.force?, file: "/dev/stdout")
|
236
|
-
when :
|
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!
|
237
271
|
raise NotImplementedError
|
272
|
+
when :environment!
|
273
|
+
puts state.bash_source
|
238
274
|
else
|
239
|
-
raise Prick::Error, "Unknown
|
275
|
+
raise Prick::Error, "Unknown dump object: #{cmd.subcommand!.__name__}"
|
240
276
|
end
|
241
277
|
|
278
|
+
|
242
279
|
else
|
243
280
|
raise Prick::Fail, "Internal error: Unhandled command - #{opts.subcommand.inspect}"
|
244
281
|
end
|
245
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
|
+
|
246
298
|
rescue RuntimeError, IOError, ShellOpts::Failure, Prick::Fail, Prick::Build::PostgresError => ex
|
247
299
|
ShellOpts.failure(ex.message)
|
248
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
|
+
file = nil
|
36
|
+
node = nil
|
37
|
+
|
35
38
|
# A SQL batch allows the first node to be an evaluated or executed node
|
36
39
|
if nodes.first.is_a?(CommandNode)
|
37
|
-
|
38
|
-
|
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::
|
44
|
-
error, line,
|
45
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
151
|
+
raise PostgresError.new(ex.message)
|
143
152
|
end
|
144
153
|
}
|
145
154
|
t_type.emit
|
data/lib/builder/builder.rb
CHANGED
@@ -10,7 +10,18 @@ include Constrain
|
|
10
10
|
module Prick
|
11
11
|
module Build
|
12
12
|
class Error < StandardError; end
|
13
|
-
class PostgresError < Error
|
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/parser.rb
CHANGED
@@ -34,7 +34,7 @@ module Prick
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
# Search for an executable in path
|
37
|
+
# Search for an executable in path. Return nil if not found
|
38
38
|
def find_executable(filename) # ChatGPT
|
39
39
|
Prick.state.executable_search_path.split(File::PATH_SEPARATOR).each do |directory|
|
40
40
|
path = File.join(directory, filename)
|
@@ -43,80 +43,37 @@ module Prick
|
|
43
43
|
nil
|
44
44
|
end
|
45
45
|
|
46
|
-
# Expand
|
47
|
-
#
|
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
|
48
49
|
#
|
49
|
-
#
|
50
|
-
# (online)
|
51
|
-
# development
|
52
|
-
# online
|
53
|
-
# offline
|
54
|
-
# backend
|
55
|
-
# frontend
|
56
|
-
# test
|
57
|
-
#
|
58
|
-
# If an environment doesn't exist in #dir, then the higher level
|
59
|
-
# environments are tried in turn. Eg. if the current environment is
|
60
|
-
# 'test', the algorithm expands '$ENVIRONMENT' to 'test', 'offline', and
|
61
|
-
# 'development' until an existing file is found. If no file is found the
|
62
|
-
# the unexpanded value is returned
|
63
|
-
#
|
64
|
-
# The production environment acts as a online environment but not a
|
65
|
-
# development environment. This means it will first search for
|
66
|
-
# 'production', then 'online', and then stop without searching for
|
67
|
-
# 'development'
|
68
|
-
#
|
69
|
-
# NOTE: This is a hardcoded feature for an internal project. It is
|
70
|
-
# configurable in the next major version
|
71
|
-
#
|
72
|
-
# TODO: Refactor: dir is not handled well when the filename includes a
|
73
|
-
# $PWD variblea
|
50
|
+
# The hierarchy of environments is defined in the PRICK_ENVIRONMENT_FILE
|
74
51
|
#
|
75
52
|
def expand_filename(dir, filename)
|
76
|
-
|
77
|
-
|
53
|
+
environment = Prick.state.environment
|
54
|
+
bash_vars = Prick.state.bash_environment
|
78
55
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
path = "
|
85
|
-
end
|
86
|
-
return path if File.exist?(path)
|
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))
|
87
62
|
|
88
|
-
|
89
|
-
env = Prick.state.environment.to_s
|
90
|
-
while true
|
63
|
+
# Check for file (may be executable)
|
91
64
|
return path if File.exist?(path)
|
92
|
-
case env
|
93
|
-
when "production"
|
94
|
-
file = expand_variables(path, ENVIRONMENT: "online") or return nil
|
95
|
-
return file if File.exist? file
|
96
|
-
break
|
97
|
-
when "development"; break
|
98
|
-
when "online"; env = "development"
|
99
|
-
when "offline"; env = "development"
|
100
|
-
when "backend"; env = "offline"
|
101
|
-
when "frontend"; env = "offline"
|
102
|
-
when "test"; env = "offline"
|
103
|
-
else
|
104
|
-
raise Error, "Illegal env: '#{env}'"
|
105
|
-
end
|
106
|
-
path = expand_variables(template, Prick.state.bash_environment.merge(ENVIRONMENT: env))
|
107
|
-
end
|
108
65
|
|
109
|
-
|
110
|
-
|
111
|
-
return path
|
112
|
-
else
|
113
|
-
return nil
|
66
|
+
# Check for executable in search path
|
67
|
+
path = find_executable(file) and return path if file !~ /\//
|
114
68
|
end
|
69
|
+
|
70
|
+
# Return nil if not found
|
71
|
+
return nil
|
115
72
|
end
|
116
73
|
|
117
74
|
# Expand $ENVIRONMENT variable
|
118
75
|
def expand_string(string)
|
119
|
-
expand_variables(string,
|
76
|
+
expand_variables(string, Prick.state.bash_environment)
|
120
77
|
end
|
121
78
|
|
122
79
|
def parse_directory(parent, dir)
|
@@ -170,8 +127,9 @@ module Prick
|
|
170
127
|
if entry =~ /^(\S+?)(\?)?(?:\s+(.+))?\s*$/
|
171
128
|
command = $1
|
172
129
|
optional = !$2.nil?
|
173
|
-
|
174
|
-
|
130
|
+
rest = $3
|
131
|
+
args = expand_string(rest || '').split
|
132
|
+
path = expand_filename(dir, command)
|
175
133
|
path || optional or raise Error, "Can't find '#{entry}' in #{dir}/ from #{unit}"
|
176
134
|
!path.nil? or return nil
|
177
135
|
else
|
@@ -215,11 +173,12 @@ module Prick
|
|
215
173
|
SqlNode.new(unit, phase, path)
|
216
174
|
when /\.fox$/
|
217
175
|
FoxNode.new(unit, :seed, path)
|
176
|
+
when /build-.*\.yml$/
|
177
|
+
parse_build_file(unit, dir, path)
|
218
178
|
else
|
219
179
|
raise Error, "Expected executable, fox, or sql file: #{File.basename(path)} in #{dir}"
|
220
180
|
end
|
221
181
|
else
|
222
|
-
path =
|
223
182
|
raise Error, "Can't find '#{entry}' in #{dir}/ from #{unit}"
|
224
183
|
end
|
225
184
|
end
|
data/lib/ext/expand_variables.rb
CHANGED
@@ -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 (
|
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 =
|
21
|
-
|
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
data/lib/prick/constants.rb
CHANGED
@@ -36,6 +36,10 @@ module Prick
|
|
36
36
|
PRICK_PROJECT_FILE = "prick.yml"
|
37
37
|
PRICK_PROJECT_PATH = PRICK_PROJECT_FILE
|
38
38
|
|
39
|
+
# # Environment specification file
|
40
|
+
# PRICK_ENVIRONMENT_FILE = "prick.environment.yml"
|
41
|
+
# PRICK_ENVIRONMENTS_PATH = PRICK_ENVIRONMENT_FILE
|
42
|
+
|
39
43
|
# Context file
|
40
44
|
PRICK_CONTEXT_FILE = ".prick-context"
|
41
45
|
PRICK_CONTEXT_PATH = PRICK_CONTEXT_FILE
|
@@ -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,16 +27,22 @@ 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
|
-
#
|
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
|
43
|
+
|
44
|
+
# Prick project dir. This is not a constant because exe/prick can change
|
45
|
+
# directory (FIXME it can?)
|
40
46
|
def prick_dir
|
41
47
|
@prick_dir ||= Dir.getwd
|
42
48
|
end
|
@@ -46,52 +52,54 @@ module Prick
|
|
46
52
|
@executable_search_path ||= "#{ENV['PATH']}:#{prick_dir}/#{BIN_DIR}:#{prick_dir}/#{LIBEXEC_DIR}"
|
47
53
|
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
h = YAML.load(File.read PRICK_CONTEXT_PATH)
|
82
|
-
rescue Errno::ENOENT
|
83
|
-
raise Prick::Error, "Can't open environment file: #{PRICK_CONTEXT_PATH}"
|
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
|
84
87
|
end
|
85
|
-
|
86
|
-
state.database = h["database"]
|
87
|
-
state.username = h["username"]
|
88
|
+
end
|
88
89
|
|
89
|
-
|
90
|
+
def bash_source
|
91
|
+
bash_environment(:local).map { |var,val| "export #{var}=\"#{Array(val).join(' ')}\"\n" }.join
|
92
|
+
end
|
90
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)
|
91
98
|
state
|
92
99
|
end
|
93
100
|
|
94
101
|
def save
|
102
|
+
raise NotImplementedError
|
95
103
|
h = {
|
96
104
|
"name" => name,
|
97
105
|
"title" => title,
|
@@ -126,11 +134,45 @@ module Prick
|
|
126
134
|
indent {
|
127
135
|
for method in [
|
128
136
|
:name, :title, :prick_version, :project_version, :schema_version,
|
129
|
-
:database_version, :
|
137
|
+
:database_version, :database, :username]
|
130
138
|
puts "#{method}: #{self.send method}"
|
131
139
|
end
|
140
|
+
puts "environments:"
|
141
|
+
indent { Environment.dump }
|
132
142
|
}
|
133
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
|
134
175
|
end
|
135
176
|
end
|
136
177
|
|
178
|
+
|
data/lib/prick/version.rb
CHANGED
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(
|
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,21 +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)
|
32
|
-
conn.schema.drop("public") # drop it again FIXME
|
33
|
+
super_conn.rdbms.empty!(database, public: false)
|
33
34
|
|
34
35
|
else
|
35
36
|
# Find schemas to refresh. This includes all schemas in the
|
@@ -69,9 +70,9 @@ module Prick::SubCommand
|
|
69
70
|
builder.execute conn
|
70
71
|
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
# rescue Prick::Error => ex
|
74
|
+
# $stderr.puts ex.message
|
75
|
+
# exit 1
|
75
76
|
|
76
77
|
rescue ::Command::Error => ex
|
77
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.
|
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
|
-
|
19
|
-
|
20
|
-
|
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
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.
|
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-
|
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: []
|