prick 0.39.12 → 0.40.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: 5ebdfba60b4161b0afa671e4c19abf963db5d017376c089acade485e5d895dc8
4
- data.tar.gz: a3cff45e06c66cb094fa3ca891b4d3a863d21ab52f2fb13ff1b9c2d026bb494b
3
+ metadata.gz: e6f8ea8c479a4482f6b26e33c9e83a0f61ab6078d715a014124c34d11b0ff66a
4
+ data.tar.gz: a4fbc9c66bee4866dc51c99068f835ded7c53dbceb4eedc6d5c4df61d3709c2b
5
5
  SHA512:
6
- metadata.gz: 16a1deeabd509f289eeb1fff91a2880e21794bd1124862b84f0fd9c4cad19c512d22d2733ac9cd7b9bf0b288f536739cd9b1c1da88b002f1e2c4ceb9dd2c677a
7
- data.tar.gz: 477ce9af0329c7b07a89be381f73cf47629f97b38395342dc817b0ccc051b20b1381da92e6bd71ed9cebb0f8a0b6e9a559eeaf7944b980f1c215d440f0d56f6f
6
+ metadata.gz: 4d3a33f458512e93fc42bf28abf4fff87fbb82c648b657207546a24962f0c9f27356cafd988a7411ab4a969c228155ed6992de781827ee16e7d70e9b711b4574
7
+ data.tar.gz: a38e125d8ca682a03a712fd911e89bf9190e5709dfdd5d449607b572e3e1e15e8f1bac3f903d6b8deeae715176203a6265201cd840aaac124c0e1043a991766d
data/TODO CHANGED
@@ -1,3 +1,4 @@
1
+ o Make references to file stable when using -C
1
2
  o prefix sql scripts with 'set ON_ERROR_STOP on'
2
3
  o make it possible to build only one scheme
3
4
  o cleanup connection
@@ -11,9 +12,18 @@ o Have the following entries
11
12
  sql: SQL_STATEMENT
12
13
  exec: BASH_COMMAND
13
14
  eval: BASH_COMMAND
14
- call:
15
+ call:
15
16
  ...
16
17
 
18
+ o Use multiple processes. Build order
19
+
20
+ tables
21
+ (pg_graph dump in parallel)
22
+ plpgsql functions
23
+ views
24
+ sql functions
25
+ indexes
26
+ load seeds (requires pg_graph dump)
17
27
 
18
28
  o Use mikras_root(1) to be able to run from any subdirectory
19
29
  o Add 'prick build .' and 'prick make .'
data/exe/prick CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bootsnap'
4
- begin
5
- cache_dir = "/run/user/" + `id -u`.chomp
6
- Bootsnap.setup(
7
- cache_dir: cache_dir, # Path to your cache
8
- development_mode: true, # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
9
- load_path_cache: true, # Optimize the LOAD_PATH with a cache
10
- compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
11
- compile_cache_yaml: true # Compile YAML into a cache
12
- )
13
- # Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
14
- end
3
+ #require 'bootsnap'
4
+ #begin
5
+ # cache_dir = "/run/user/" + `id -u`.chomp
6
+ # Bootsnap.setup(
7
+ # cache_dir: cache_dir, # Path to your cache
8
+ # development_mode: true, # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
9
+ # load_path_cache: true, # Optimize the LOAD_PATH with a cache
10
+ # compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
11
+ # compile_cache_yaml: true # Compile YAML into a cache
12
+ # )
13
+ # # Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
14
+ #end
15
15
 
16
16
  require 'shellopts'
17
17
 
@@ -37,9 +37,15 @@ SPEC = %(
37
37
  -e,environment-file=ENVIRONMENT-FILE
38
38
  Override the environment file. Default is 'prick.environment.yml'
39
39
 
40
+ -r,reflections-file=REFLECTIONS-FILE
41
+ Override the reflections file. Default is 'schema/reflections.yml'
42
+
40
43
  -s,state-file=STATE-FILE
41
44
  Override state file. Default is '.prick-state.yml'
42
45
 
46
+ -f,fox-state-file=FOX-STATE-FILE
47
+ Override fox state file. Default is '.fox-state.yml
48
+
43
49
  init! -n,name=NAME -t,title=TITLE DIRECTORY #@ Initialize new Prick project directory
44
50
  Initializes a prick project and checks it into git. If a directory is
45
51
  given the directory will be created and the project initialized in it.
@@ -117,7 +123,9 @@ SPEC = %(
117
123
  drop.data!
118
124
  @ Drop data
119
125
 
120
- TODO
126
+ Drop data added to the database after the last snapshot was taken. It is
127
+ useful to run 'prick snapshot' immediately after you complete building
128
+ the database
121
129
 
122
130
  build! --step -f,force -t,time --dump=KIND? -- [SCHEMA]
123
131
  Build the project. If SCHEMA is defined, later schemas are excluded. KIND
@@ -134,12 +142,15 @@ SPEC = %(
134
142
  run! --step -t,time --dump=KIND? --schema=SCHEMA -- PATH
135
143
  @ Execute path in Prick environment
136
144
 
137
- Execute a single directory or file within the Prick environment. If given
138
- a directory argument, the directory is searched for a build.yml file. If
139
- given a file argument, the file should be a .sql, .fox, or build file
145
+ Execute a single build directory or file within the Prick environment. If
146
+ given a directory argument, the directory is searched for a build.yml
147
+ file. If given a file argument, the file should be a .sql, .fox, or build
148
+ file
140
149
 
141
150
  The --schema option sets the current schema before executing the PATH
142
151
 
152
+ TODO: Allow executables
153
+
143
154
  touch! -- [SUCCESS]
144
155
  Record a build. SUCCESS can be 'true' (default) or 'false'. 'touch' is
145
156
  used by external build processes that doesn't involve prick
@@ -148,18 +159,19 @@ SPEC = %(
148
159
  Emit a bash script to build the database. The script is constructed from
149
160
  the build attributes in the environment file
150
161
 
151
- fox! -- FILE...
152
- Load fox file data. Data are reset to their initial state after build
153
- before the fox data are loaded. This makes it possible to experiment with
154
- different data sets
162
+ fox! -r,reset +e,exclude=SCHEMA... -- FILE...
163
+ Load fox file data. Can be used together with 'prick snapshot' and 'prick
164
+ drop data' to to experiment with different data sets
155
165
 
156
- # TODO: A --clean option that resets data
157
- #
166
+ The --reset option resets data to the last snapshot before loading the
167
+ file. The --exclude option lists schemas that should not be included in
168
+ fox, typically schemas that doens't comply with prick's naming
169
+ conventions
158
170
 
159
171
  snapshot!
160
- Records maximum ID for all tables and stores them in prick.snapshot. It is
161
- used by 'drop data' to reset data to the point in time of the snapshot.
162
- Only one snapshot can be created at a time
172
+ Records maximum ID for all tables and stores them in the prick.snapshot
173
+ table. It is used by 'drop data' to reset data to the point in time of
174
+ the snapshot. Only one snapshot can be created at a time
163
175
 
164
176
  reset!
165
177
  Restore database to the last snapshot. TODO
@@ -240,6 +252,10 @@ def clean_pg_meta_cache
240
252
  FileUtils.rm_f(PG_META_STATE_PATH)
241
253
  end
242
254
 
255
+ def clean_fox_state
256
+ FileUtils.rm_f(Prick.state.fox_state_file)
257
+ end
258
+
243
259
  def parse_database_args(state, args)
244
260
  arg, environment = args.expect(0..2)
245
261
  if arg
@@ -273,7 +289,7 @@ end
273
289
 
274
290
  # Require prick only after -C directory option because some constants depends
275
291
  # on the current directory
276
- require_relative '../lib/prick.rb'
292
+ require_relative '../lib/prick-command.rb'
277
293
  include Prick
278
294
 
279
295
  begin
@@ -287,7 +303,9 @@ begin
287
303
  # Handle -p, -e, and -c. TODO: Take -C into account for relative paths. Low-hanging fruit
288
304
  project_file = opts.project_file || PRICK_PROJECT_PATH
289
305
  environment_file = opts.environment_file || PRICK_ENVIRONMENT_PATH
306
+ reflections_file = opts.reflections_file || REFLECTIONS_PATH
290
307
  state_file = opts.state_file || PRICK_STATE_PATH
308
+ fox_state_file = opts.fox_state_file || FOX_STATE_PATH
291
309
 
292
310
  # Process init command and exit
293
311
  if opts.subcommand == :init!
@@ -307,7 +325,7 @@ begin
307
325
  end
308
326
 
309
327
  # Load state
310
- state = Prick.state = State.new(project_file, environment_file, state_file)
328
+ state = Prick.state = State.new(project_file, environment_file, reflections_file, state_file, fox_state_file)
311
329
 
312
330
  # Lazyness
313
331
  database = state.database
@@ -381,6 +399,7 @@ begin
381
399
  when :build!
382
400
  require_db
383
401
  clean_pg_meta_cache
402
+ clean_fox_state
384
403
  dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
385
404
  # exclude = cmd.exclude? ? cmd.exclude.split(",") : []
386
405
  Prick::SubCommand.build(
@@ -390,6 +409,7 @@ begin
390
409
  when :make!
391
410
  require_db
392
411
  clean_pg_meta_cache
412
+ clean_fox_state
393
413
  dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
394
414
  Prick::SubCommand.make(
395
415
  database, username, args.expect(0..1),
@@ -415,7 +435,7 @@ begin
415
435
 
416
436
  when :fox!
417
437
  require_db
418
- Prick::SubCommand.fox(database, username, args)
438
+ Prick::SubCommand.fox(database, username, args, reset: cmd.reset?, exclude: cmd.exclude)
419
439
 
420
440
  when :snapshot!
421
441
  require_db
@@ -40,7 +40,7 @@ module Prick
40
40
 
41
41
  begin
42
42
  # A SQL batch allows the first node to be an evaluated or executed node
43
- if nodes.first.is_a?(CommandNode)
43
+ if nodes.first.is_a?(ExecutableNode)
44
44
  node = nodes.shift
45
45
  conn.execute [node.source], silent: true
46
46
  end
@@ -122,8 +122,12 @@ module Prick
122
122
  t_type.stop
123
123
  t_fox = Timer.new " Parse files"
124
124
 
125
+ # Fetch state
126
+ ids, anchors = FixtureFox::Fox.read_state(type, Prick.state.fox_state_file)
127
+
125
128
  # Create fox object
126
- fox = FixtureFox::Fox.new(type)
129
+ #
130
+ fox = FixtureFox::Fox.new(type, ids: ids, anchors: anchors)
127
131
 
128
132
  # Parse files
129
133
  for file in files
@@ -135,8 +139,8 @@ module Prick
135
139
  fox.assign_types
136
140
  fox.generate
137
141
 
138
- # Dump state file
139
- fox.write_state(FOX_STATE_PATH)
142
+ # Dump new state file
143
+ fox.write_state(Prick.state.fox_state_file)
140
144
 
141
145
  # Timer
142
146
  t_fox.stop
@@ -66,7 +66,7 @@ module Prick
66
66
 
67
67
  @conn = conn
68
68
  @path = path
69
- @reflections_file = REFLECTIONS_PATH
69
+ @reflections_file = Prick.state.reflections_file
70
70
  @clean = clean
71
71
  @single = single
72
72
  @step = step
@@ -43,7 +43,7 @@ module Prick
43
43
  @source_lines = nil
44
44
  end
45
45
 
46
- def to_s() @to_s ||= [path, args].compact.join(" ") end
46
+ def to_s() @to_s ||= [path, *args].join(" ") end
47
47
  def inspect() to_s end
48
48
  def dump() puts "#{inspect} (#{@schema || 'nil'})" end
49
49
 
@@ -123,47 +123,132 @@ module Prick
123
123
  def self.objects() @@objects end
124
124
  end
125
125
 
126
- # Virtual base class for EvalNode and ExecNode
127
- class CommandNode < Node
126
+ # Virtual base class for executable nodes
127
+ class ExecutableNode < Node
128
+ # The full command including arguments
128
129
  alias_method :command, :to_s
129
130
 
131
+ # The command. Equal to #command without arguments
132
+ alias_method :cmd, :path
133
+
134
+ # Path to executable
135
+ def exepath()
136
+ @exepath ||= begin
137
+ v = `which #{path} 2>/dev/null`.chomp
138
+ v == "" ? nil : v
139
+ end
140
+ end
141
+
130
142
  def filename = File.basename(path)
131
143
 
132
144
  def initialize(parent, phase, path, args = nil)
133
145
  constrain args, [String]
134
146
  super(parent, phase, :exe, path, args)
147
+ check_path
135
148
  end
136
149
 
137
150
  def inspect() "#{path} #{(args || []).join(" ")}" end
138
151
 
139
152
  protected
140
- def execute_command
153
+ def check_path
154
+ !exepath.nil? or Prick.error "'#{path}' is not an executable"
155
+ end
156
+
157
+ def read_cmd
158
+ sql = execute
159
+ @source_lines = 1 + 1 + sql.count("\n")
160
+ ["set search_path to #{schema}, pg_temp", sql.join("\n")]
161
+ end
162
+
163
+ def exec_cmd
164
+ execute
165
+ []
166
+ end
167
+
168
+ def execute() = raise
169
+
170
+ def execute_system
141
171
  begin
142
- Command.command(Prick.state.bash_environment, command)
172
+ yield
143
173
  rescue Command::Error => ex
144
174
  message = "Error executing '#{command}'\n" + ex.stderr.map { |l| " #{l}" }.join("\n")
145
175
  Prick.error message
146
176
  end
147
177
  end
178
+
179
+ def execute_script
180
+ begin
181
+ r = Command.script(Prick.state.bash_environment, cmd, argv: args)
182
+ rescue Command::Error => ex
183
+ message = "Error executing '#{command}'\n" + ex.stderr.map { |l| " #{l}" }.join("\n")
184
+ Prick.error message
185
+ ensure
186
+ # Reset connection because the script may have manipulated the
187
+ # current connection
188
+ Prick.owner_conn&.reset
189
+ end
190
+ r
191
+ end
148
192
  end
149
193
 
150
- # EvalNode executes the command and feeds the output into the database
151
- class EvalNode < CommandNode
194
+ class ScriptNode < ExecutableNode
195
+ protected
196
+ alias_method :execute, :execute_script
197
+
198
+ def check_path
199
+ super
200
+ return if path.end_with?(".rb")
201
+ first_line = File.open(exepath, &:gets).chomp
202
+ first_line =~ /^#!\/usr\/bin\/env\>.*ruby/ or Prick.error "'#{path}' is not a ruby script"
203
+ end
204
+ end
205
+
206
+ class EvalNode < ScriptNode
207
+ alias_method :read_source, :read_cmd
208
+ end
209
+
210
+ class ExecNode < ScriptNode
211
+ alias_method :read_source, :exec_cmd
212
+ end
213
+
214
+ class SystemCommandNode < ExecutableNode
215
+ protected
216
+ alias_method :execute, :execute_system
217
+ end
218
+
219
+ class RequireNode < ExecutableNode
152
220
  def read_source
153
221
  sql = execute_command
154
222
  @source_lines = 1 + 1 + sql.count("\n")
155
223
  ["set search_path to #{schema}, pg_temp", sql.join("\n")]
156
224
  end
225
+
226
+ def execute_command
227
+ execute {
228
+ Command.inline(Prick.state.bash_environment, path, argv: args)
229
+ }
230
+ end
157
231
  end
158
232
 
159
- # ExecNode executes the command and ignores its output
160
- class ExecNode < CommandNode
161
- def read_source
162
- execute_command
163
- []
233
+ class CommandNode < ExecutableNode
234
+ protected
235
+ def execute_command
236
+ execute {
237
+ Command.command(Prick.state.bash_environment, command)
238
+ }
164
239
  end
165
240
  end
166
241
 
242
+ # EvalNode executes the command and feeds the output into the database
243
+ class SysEvalNode < SystemCommandNode
244
+ alias_method :read_source, :read_cmd
245
+ end
246
+
247
+ # ExecNode executes the command and ignores its output
248
+ class SysExecNode < SystemCommandNode
249
+ alias_method :read_source, :exec_cmd
250
+ end
251
+
167
252
  # A build.yml file node
168
253
  class BuildNode < Node
169
254
  # True if the build file contains a 'schema' attribute
@@ -220,4 +305,3 @@ module Prick
220
305
  end
221
306
  end
222
307
  end
223
-
@@ -57,7 +57,8 @@ module Prick
57
57
  unit = make_build_unit(parent, path)
58
58
  entries = YAML.load(File.read(path)) || []
59
59
  entries.each { |entry|
60
- if entry.is_a?(Hash) && (entry.size != 1 || !%w(call sql exec eval).include?(entry.first.first))
60
+ if entry.is_a?(Hash) \
61
+ && (entry.size != 1 || !%w(call sql exec eval sysexec syseval).include?(entry.first.first))
61
62
  entry.each { |key, value|
62
63
  if key == "schema"
63
64
  unit.schema = value
@@ -129,12 +130,21 @@ module Prick
129
130
  command = args.shift
130
131
  klass && command or raise "Illegal number of arguments: #{value}"
131
132
  ModuleNode.new(unit, phase, path, klass, command, args)
133
+ when "require"
134
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
135
+ RequireNode.new(unit, phase, path, args)
132
136
  when "eval"
133
137
  (path, args = parse_file_entry(unit, dir, value)) or return nil
134
138
  EvalNode.new(unit, phase, path, args)
135
139
  when "exec"
136
140
  (path, args = parse_file_entry(unit, dir, value)) or return nil
137
141
  ExecNode.new(unit, phase, path, args)
142
+ when "syseval"
143
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
144
+ SysEvalNode.new(unit, phase, path, args)
145
+ when "sysexec"
146
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
147
+ SysExecNode.new(unit, phase, path, args)
138
148
  else
139
149
  raise Error, "Illegal key: #{key}"
140
150
  end
@@ -143,7 +153,7 @@ module Prick
143
153
  if File.directory? path
144
154
  parse_directory(unit, path)
145
155
  elsif File.executable?(path)
146
- ExecNode.new(unit, phase, path, args)
156
+ SysExecNode.new(unit, phase, path, args)
147
157
  elsif File.file? path
148
158
  case path
149
159
  when /\.sql$/
@@ -1,5 +1,4 @@
1
1
  require 'fcntl'
2
-
3
2
  module Command
4
3
  class Error < RuntimeError
5
4
  attr_reader :cmd
@@ -18,92 +17,85 @@ module Command
18
17
  end
19
18
  end
20
19
 
21
- class Pipe
22
- attr_reader :cmd
23
- attr_reader :status
24
-
25
- def writer() @pw[1] end
26
- def reader() @pr[0] end
27
- def error() @pe[0] end
28
-
29
- forward_to :reader, :read, :gets
30
- forward_to :writer, :write, :puts
31
-
32
- # Remaining output not consumed by #get/#read or through #reader or #error
33
- attr_reader :stdout
34
- attr_reader :stdin
20
+ # Execute the shell command 'cmd' and return standard-output as an array of
21
+ # strings. If :stdin is a string or an array of lines if will be fed to the
22
+ # command on standard-input, if it is a IO object that IO object is piped to
23
+ # the command
24
+ #
25
+ # By default #command pass through error message to stderr but if :stderr is
26
+ # true, #command will instead return a tuple of stdout/stderr lines. If
27
+ # :stderr is false, stderr is ignored and is the same as adding "2>/dev/null"
28
+ # to the command
29
+ #
30
+ # #command raises a Command::Error exception if the command returns with an
31
+ # exit code != 0 unless :fail is false. In that case the the exit code can be
32
+ # fetched from Command::status
33
+ #
34
+ def command(env = {}, cmd, argv: nil, stdin: nil, stderr: nil, fail: true)
35
+ command_wrapper(cmd, stdin: stdin, stderr: stderr, fail: fail) {
36
+ # Add standard shell options
37
+ bashcmd = "set -o errexit\nset -o pipefail\n#{cmd}"
35
38
 
36
- def initialize(cmd, stderr: nil, fail: true)
37
- @cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
38
- @stderr = stderr
39
- @fail = fail
39
+ # Add arguments if present
40
+ bashcmd = [bashcmd, *argv].join(' ') if argv
40
41
 
41
- @pw = IO::pipe # pipe[0] for read, pipe[1] for write
42
- @pr = IO::pipe
43
- @pe = IO::pipe
42
+ # Clean bundler environment so that prick can be run in development mode.
43
+ # The problem is that the bundler environment is inherited by
44
+ # subprocesses and interferes with loading ruby commands. FIXME This is
45
+ # only relevant when running prick in development mode
46
+ ENV.delete_if { |k,v| %w(RUBYOPT RUBYLIB _).include?(k) || k =~ /^BUNDLER?_/ }
44
47
 
45
- STDOUT.flush
48
+ # Setup environment
49
+ env = env.map { |k,v| [k.to_s, v.to_s] }.to_h # Convert array values to strings
50
+ env.each { |k,v| ENV[k] = v }
46
51
 
47
- @pid = fork {
48
- @pw[1].close
49
- @pr[0].close
50
- @pe[0].close
52
+ Kernel.exec(env, bashcmd)
53
+ }
54
+ end
51
55
 
52
- STDIN.reopen(@pw[0])
53
- @pw[0].close
56
+ # Like command but returns true if the command exited with the expected
57
+ # status. Note that it suppresses standard-error by default
58
+ #
59
+ def command?(env = {}, cmd, expect: 0, argv: nil, stdin: nil, stderr: false)
60
+ command(env, cmd, argv: argv, stdin: stdin, stderr: stderr, fail: false)
61
+ @status == expect
62
+ end
54
63
 
55
- STDOUT.reopen(@pr[1])
56
- @pr[1].close
64
+ # Exit status of the last command
65
+ def status() @status end
57
66
 
58
- STDERR.reopen(@pe[1])
59
- @pe[1].close
67
+ # Exception of the last command if it failed, otherwise nil TODO What is this used for?
68
+ def exception() @exception end
60
69
 
61
- exec(cmd)
62
- }
70
+ # Like #command but "execute" the ruby file by requiring it. This is only
71
+ # possible using ruby scripts
72
+ def script(env = {}, script, argv: nil, stdin: nil, stderr: nil, fail: true)
63
73
 
64
- @pw[0].close
65
- @pr[1].close
66
- @pe[1].close
74
+ # Turn script into absolute path or a path relative to the current
75
+ # directory
76
+ if !script.start_with?("/") && !script.start_with?(".")
77
+ script = "./#{script}"
67
78
  end
68
79
 
69
- def wait
70
- @pw[1].close
71
- @status = Process.waitpid2(@pid)[1].exitstatus
72
-
73
- out = @pr[0].readlines.map(&:chomp)
74
- err = @pe[0].readlines.map(&:chomp)
80
+ command_wrapper(script, stdin: stdin, stderr: stderr, fail: fail) {
81
+ ENV.delete_if { |k,v| %w(RUBYOPT RUBYLIB _).include?(k) || k =~ /^BUNDLER?_/ }
75
82
 
76
- @pr[0].close
77
- @pe[0].close
83
+ # Setup environment
84
+ env = env.map { |k,v| [k.to_s, v.to_s] }.to_h # Convert array values to strings
85
+ env.each { |k,v| ENV[k] = v }
78
86
 
79
- raise Command::Error.new(@cmd, @status, nil, out, err) if @status != 0 && @fail == true
87
+ # Setup ARGV. Only relevant when running ruby scripts using the 'require'
88
+ # mechanish instead of calling them through the shell
89
+ ARGV.replace (argv || [])
80
90
 
81
- case @stderr
82
- when true; [out, err]
83
- when false; out
84
- when nil
85
- $stderr.puts err
86
- out
87
- end
88
- end
91
+ # 'Execute' script by requiring it
92
+ require script
93
+ }
89
94
  end
90
95
 
91
- # Execute the shell command 'cmd' and return standard-output as an array of
92
- # strings. If :stdin is a string or an array of lines if will be fed to the
93
- # command on standard-input, if it is a IO object that IO object is piped to
94
- # the command
95
- #
96
- # By default #command pass through stderr but if :stderr is true, #command
97
- # will instead return a tuple of stdout/stderr lines. If :stderr is false,
98
- # stderr is ignored and is the same as adding "2>/dev/null" to the command
99
- #
100
- # #command raises a Command::Error exception if the command returns with an
101
- # exit code != 0 unless :fail is false. In that case the the exit code can be
102
- # fetched from Command::status
103
- #
104
- def command(env = {}, cmd, stdin: nil, stderr: nil, fail: true)
105
- cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
106
-
96
+ private
97
+ # cmd is the of the command/script and is only used in error messages
98
+ def command_wrapper(cmd, stdin: nil, stderr: nil, fail: true, &block)
107
99
  pw = IO::pipe # pipe[0] for read, pipe[1] for write
108
100
  pr = IO::pipe
109
101
  pe = IO::pipe
@@ -124,14 +116,7 @@ module Command
124
116
  STDERR.reopen(pe[1])
125
117
  pe[1].close
126
118
 
127
- # Clean bundler environment so that prick can be run in development mode.
128
- # The problem is that the bundler environment is inherited by
129
- # subprocesses and interferes with loading ruby commands. FIXME This is
130
- # only relevant when running prick in development mode
131
- ENV.delete_if { |k,v| %w(RUBYOPT RUBYLIB _).include?(k) || k =~ /^BUNDLER?_/ }
132
- env = env.map { |k,v| [k.to_s, v.to_s] }.to_h # Convert array values to strings
133
- env.each { |k,v| ENV[k] = v }
134
- Kernel.exec(env, cmd)
119
+ yield
135
120
  }
136
121
 
137
122
  pw[0].close
@@ -172,23 +157,82 @@ module Command
172
157
  end
173
158
  end
174
159
 
175
- # Like command but returns true if the command exited with the expected
176
- # status. Note that it suppresses standard-error by default
177
- #
178
- def command?(cmd, expect: 0, stdin: nil, stderr: false)
179
- command(cmd, stdin: stdin, stderr: stderr, fail: false)
180
- @status == expect
181
- end
182
-
183
- # Exit status of the last command
184
- def status() @status end
185
-
186
- # Exception of the last command if it failed, otherwise nil TODO What is this used for?
187
- def exception() @exception end
188
-
189
160
  module_function :command
161
+ module_function :command?
162
+ module_function :script
190
163
  module_function :status
191
164
  module_function :exception
192
- module_function :command?
165
+ module_function :command_wrapper
193
166
  end
194
167
 
168
+ __END__
169
+ class Pipe
170
+ attr_reader :cmd
171
+ attr_reader :status
172
+
173
+ def writer() @pw[1] end
174
+ def reader() @pr[0] end
175
+ def error() @pe[0] end
176
+
177
+ forward_to :reader, :read, :gets
178
+ forward_to :writer, :write, :puts
179
+
180
+ # Remaining output not consumed by #get/#read or through #reader or #error
181
+ attr_reader :stdout
182
+ attr_reader :stdin
183
+
184
+ def initialize(cmd, stderr: nil, fail: true)
185
+ @cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
186
+ @stderr = stderr
187
+ @fail = fail
188
+
189
+ @pw = IO::pipe # pipe[0] for read, pipe[1] for write
190
+ @pr = IO::pipe
191
+ @pe = IO::pipe
192
+
193
+ STDOUT.flush
194
+
195
+ @pid = fork {
196
+ @pw[1].close
197
+ @pr[0].close
198
+ @pe[0].close
199
+
200
+ STDIN.reopen(@pw[0])
201
+ @pw[0].close
202
+
203
+ STDOUT.reopen(@pr[1])
204
+ @pr[1].close
205
+
206
+ STDERR.reopen(@pe[1])
207
+ @pe[1].close
208
+
209
+ exec(cmd)
210
+ }
211
+
212
+ @pw[0].close
213
+ @pr[1].close
214
+ @pe[1].close
215
+ end
216
+
217
+ def wait
218
+ @pw[1].close
219
+ @status = Process.waitpid2(@pid)[1].exitstatus
220
+
221
+ out = @pr[0].readlines.map(&:chomp)
222
+ err = @pe[0].readlines.map(&:chomp)
223
+
224
+ @pr[0].close
225
+ @pe[0].close
226
+
227
+ raise Command::Error.new(@cmd, @status, nil, out, err) if @status != 0 && @fail == true
228
+
229
+ case @stderr
230
+ when true; [out, err]
231
+ when false; out
232
+ when nil
233
+ $stderr.puts err
234
+ out
235
+ end
236
+ end
237
+ end
238
+
data/lib/prick/state.rb CHANGED
@@ -24,9 +24,16 @@ module Prick
24
24
  # absent if the project doesn't use environments
25
25
  attr_reader :environment_file
26
26
 
27
+ # Reflections file. Default 'schema/reflections.yml'. May be nil if the
28
+ # file is absent
29
+ attr_reader :reflections_file
30
+
27
31
  # State file. Default '.prick-state.yml'
28
32
  attr_reader :state_file
29
33
 
34
+ # Fox state file. Default '.fox-state.yml'
35
+ attr_reader :fox_state_file
36
+
30
37
  # Schema data file. FIXME What is this?
31
38
  def schema_file() SCHEMA_VERSION_PATH end
32
39
 
@@ -95,8 +102,9 @@ module Prick
95
102
  @clean = Git.clean?
96
103
  end
97
104
 
98
- def initialize(project_file, environment_file, state_file)
99
- @project_file, @environment_file, @state_file = project_file, environment_file, state_file
105
+ def initialize(project_file, environment_file, reflections_file, state_file, fox_state_file)
106
+ @project_file, @environment_file, @reflections_file, @state_file, @fox_state_file =
107
+ project_file, environment_file, reflections_file, state_file, fox_state_file
100
108
  @project_loaded = @state_loaded = @environment_loaded = false
101
109
 
102
110
  if @project_file && File.exist?(@project_file)
@@ -3,8 +3,16 @@
3
3
  require_relative '../builder/builder.rb'
4
4
 
5
5
  module Prick::SubCommand
6
- def self.fox(database, username, files)
7
- Command.command "fox --state=#{FOX_STATE_PATH} --exec #{database} #{files.join(" ")}"
6
+ def self.fox(database, username, files, reset: false, exclude: nil)
7
+ self.drop_data(database) # In prick-drop
8
+ opts = {
9
+ state: FOX_STATE_PATH,
10
+ delete: "none",
11
+ exec: true,
12
+ exclude: (exclude ? exclude.join(',') : nil),
13
+ reflections: Prick.state.reflections_file
14
+ }.reject { |k,v| v.nil? }.map { |k,v| "--#{k}#{v == true ? "" : "=#{v}"}" }.join(" ")
15
+ Command.command "fox #{opts} #{database} #{files.join(" ")}"
8
16
  end
9
17
  end
10
18
 
@@ -31,7 +31,7 @@ module Prick::SubCommand
31
31
  Command.status == 0 or Prick.failure "Failed creating initial import"
32
32
 
33
33
  # Write (valid) configuration file
34
- state = State.new(project_file, nil, nil)
34
+ state = State.new(project_file, nil, nil, nil, nil)
35
35
  state.name = name
36
36
  state.title = title
37
37
  state.prick_version = PrickVersion.new VERSION
@@ -4,7 +4,7 @@ require_relative '../builder/builder.rb'
4
4
 
5
5
  module Prick::SubCommand
6
6
  def self.snapshot(
7
- database, username,
7
+ database, username,
8
8
  builddir: Prick.state.schema_dir)
9
9
 
10
10
  conn = Prick.state.connection
@@ -24,6 +24,13 @@ module Prick::SubCommand
24
24
  }
25
25
  end
26
26
  conn.insert("prick", "snapshots", records)
27
+
28
+ # Patch fox state file
29
+ state = YAML.load(IO.read(Prick.state.fox_state_file))
30
+ for record in records
31
+ state[:ids][record[:schema_name] + '.' + record[:table_name]] = record[:max_id] if record[:max_id]
32
+ end
33
+ IO.write(Prick.state.fox_state_file, YAML.dump(state))
27
34
  end
28
35
  end
29
36
 
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.39.12"
4
+ VERSION = "0.40.0"
5
5
  end
@@ -0,0 +1,123 @@
1
+ require 'prick/version.rb'
2
+
3
+ require 'pg_conn'
4
+
5
+ require 'indented_io'
6
+ require 'constrain'
7
+ require 'forward_to'
8
+ require 'string-text'
9
+
10
+ include Constrain
11
+ include ForwardTo
12
+ include String::Text
13
+
14
+ require 'fixture_fox'
15
+
16
+ module Prick
17
+ class Error < RuntimeError; end
18
+ class Failure < RuntimeError; end
19
+ end
20
+
21
+ require_relative 'prick/constants.rb'
22
+ require_relative 'prick/ext/expand_variables.rb'
23
+
24
+ require_relative 'prick/local/command.rb'
25
+ require_relative 'prick/local/git.rb'
26
+ require_relative 'prick/local/timer.rb'
27
+ require_relative 'prick/local/ansi.rb'
28
+ require_relative 'prick/local/fmt.rb'
29
+
30
+ require_relative 'prick/environment.rb'
31
+ require_relative 'prick/state.rb'
32
+ require_relative 'prick/prick_version.rb'
33
+ require_relative 'prick/diff.rb'
34
+
35
+ module Prick
36
+ def mesg(*msgs) = Prick.mesg(*msgs)
37
+ def verb(*msgs) = Prick.verb(*msgs)
38
+ def self.mesg(*msgs) puts msgs.join(" ") if !$quiet end
39
+ def self.verb(*msgs) puts msgs.join(" ") if $verbose && !$quiet end
40
+ end
41
+
42
+ require_relative 'prick/subcommand/subcommand.rb'
43
+ require_relative 'prick/subcommand/prick-bash.rb'
44
+ require_relative 'prick/subcommand/prick-build.rb'
45
+ require_relative 'prick/subcommand/prick-clean.rb'
46
+ require_relative 'prick/subcommand/prick-create.rb'
47
+ require_relative 'prick/subcommand/prick-drop.rb'
48
+ require_relative 'prick/subcommand/prick-fox.rb'
49
+ require_relative 'prick/subcommand/prick-init.rb'
50
+ require_relative 'prick/subcommand/prick-list.rb'
51
+ require_relative 'prick/subcommand/prick-make.rb'
52
+ require_relative 'prick/subcommand/prick-migrate.rb'
53
+ require_relative 'prick/subcommand/prick-release.rb'
54
+ require_relative 'prick/subcommand/prick-run.rb'
55
+ require_relative 'prick/subcommand/prick-set.rb'
56
+ require_relative 'prick/subcommand/prick-setup.rb'
57
+ require_relative 'prick/subcommand/prick-snapshot.rb'
58
+ require_relative 'prick/subcommand/prick-teardown.rb'
59
+ require_relative 'prick/subcommand/prick-touch.rb'
60
+
61
+ module Prick
62
+ @state = nil
63
+ def self.state?() !@state.nil? end
64
+ def self.state() @state or raise ArgumentError end
65
+ def self.state=(state) @state = state end
66
+
67
+ def self.error(*args) raise Prick::Error.new *args end
68
+ def self.failure(*args) raise Prick::Failure.new *args end
69
+
70
+ def self.owner_conn = state.connection
71
+ def self.super_conn = State.connection
72
+ # def self.conn = owner_conn
73
+
74
+ # Return list of prick databases. If a block is given it is called with a
75
+ # connection to each of the databases
76
+ def self.databases(&block)
77
+ databases = super_conn.values %(
78
+ select datname from pg_database where datistemplate = false order by datname
79
+ )
80
+ databases.select! { |database|
81
+ PgConn.new(database) { |conn|
82
+ is_prick_database = conn.exist?(%(
83
+ select 1
84
+ from information_schema.tables
85
+ where table_schema = 'prick'
86
+ and table_name = 'builds'
87
+ limit 1
88
+ ))
89
+ yield(database, conn) if is_prick_database && block_given?
90
+ is_prick_database
91
+ }
92
+ }
93
+ databases
94
+ end
95
+
96
+ # Call a ruby script using fork. This is much faster than calling the script
97
+ # using #system and it shares the database connection with the current
98
+ # process
99
+ #
100
+ # The script is supposed to require 'prick.rb' to get access to the
101
+ # Prick.conn function that returns the current database when run in a Prick
102
+ # environment
103
+ #
104
+ # require 'prick.rb'
105
+ # conn = Prick.conn database, username
106
+ #
107
+ def self.call(ruby_script)
108
+ File.exist?(ruby_script) or raise ArgumentError, "Can't find script '#{ruby_script}' in Prick.call"
109
+
110
+ pid = fork do
111
+ require(File.join(Dir.getwd, ruby_script))
112
+ end
113
+ Process.wait(pid)
114
+ $?.exitstatus == 0 or raise ArgumentError, "Ruby script '#{ruby_script}' failed"
115
+ owner_conn.reset
116
+ end
117
+
118
+ module SubCommand
119
+ def self.owner_conn = Prick.owner_conn
120
+ def self.super_conn = Prick.super_conn
121
+ # def self.conn = Prick.conn
122
+ end
123
+ end
data/lib/prick.rb CHANGED
@@ -1,101 +1,12 @@
1
- require 'prick/version.rb'
2
1
 
3
2
  require 'pg_conn'
4
3
 
5
- require 'indented_io'
6
- require 'constrain'
7
- require 'forward_to'
8
- require 'string-text'
9
-
10
- include Constrain
11
- include ForwardTo
12
- include String::Text
13
-
14
- require 'fixture_fox'
15
-
16
- module Prick
17
- class Error < RuntimeError; end
18
- class Failure < RuntimeError; end
19
- end
20
-
21
- require_relative 'prick/constants.rb'
22
- require_relative 'prick/ext/expand_variables.rb'
23
-
24
- require_relative 'prick/local/command.rb'
25
- require_relative 'prick/local/git.rb'
26
- require_relative 'prick/local/timer.rb'
27
- require_relative 'prick/local/ansi.rb'
28
- require_relative 'prick/local/fmt.rb'
29
-
30
- require_relative 'prick/environment.rb'
31
- require_relative 'prick/state.rb'
32
- require_relative 'prick/prick_version.rb'
33
- require_relative 'prick/diff.rb'
34
-
35
4
  module Prick
36
- def mesg(*msgs) = Prick.mesg(*msgs)
37
- def verb(*msgs) = Prick.verb(*msgs)
38
- def self.mesg(*msgs) puts msgs.join(" ") if !$quiet end
39
- def self.verb(*msgs) puts msgs.join(" ") if $verbose && !$quiet end
40
- end
41
-
42
- require_relative 'prick/subcommand/subcommand.rb'
43
- require_relative 'prick/subcommand/prick-bash.rb'
44
- require_relative 'prick/subcommand/prick-build.rb'
45
- require_relative 'prick/subcommand/prick-clean.rb'
46
- require_relative 'prick/subcommand/prick-create.rb'
47
- require_relative 'prick/subcommand/prick-drop.rb'
48
- require_relative 'prick/subcommand/prick-fox.rb'
49
- require_relative 'prick/subcommand/prick-init.rb'
50
- require_relative 'prick/subcommand/prick-list.rb'
51
- require_relative 'prick/subcommand/prick-make.rb'
52
- require_relative 'prick/subcommand/prick-migrate.rb'
53
- require_relative 'prick/subcommand/prick-release.rb'
54
- require_relative 'prick/subcommand/prick-run.rb'
55
- require_relative 'prick/subcommand/prick-set.rb'
56
- require_relative 'prick/subcommand/prick-setup.rb'
57
- require_relative 'prick/subcommand/prick-snapshot.rb'
58
- require_relative 'prick/subcommand/prick-teardown.rb'
59
- require_relative 'prick/subcommand/prick-touch.rb'
60
-
61
- module Prick
62
- @state = nil
63
- def self.state?() !@state.nil? end
64
- def self.state() @state or raise ArgumentError end
65
- def self.state=(state) @state = state end
66
-
67
- def self.error(*args) raise Prick::Error.new *args end
68
- def self.failure(*args) raise Prick::Failure.new *args end
69
-
70
- def self.owner_conn = state.connection
71
- def self.super_conn = State.connection
72
- # def self.conn = owner_conn
73
-
74
- # Return list of prick databases. If a block is given it is called with a
75
- # connection to each of the databases
76
- def self.databases(&block)
77
- databases = super_conn.values %(
78
- select datname from pg_database where datistemplate = false order by datname
79
- )
80
- databases.select! { |database|
81
- PgConn.new(database) { |conn|
82
- is_prick_database = conn.exist?(%(
83
- select 1
84
- from information_schema.tables
85
- where table_schema = 'prick'
86
- and table_name = 'builds'
87
- limit 1
88
- ))
89
- yield(database, conn) if is_prick_database && block_given?
90
- is_prick_database
91
- }
92
- }
93
- databases
94
- end
95
-
96
- module SubCommand
97
- def self.owner_conn = Prick.owner_conn
98
- def self.super_conn = Prick.super_conn
99
- # def self.conn = Prick.conn
5
+ def self.conn(database, username)
6
+ if self.respond_to?(:owner_conn)
7
+ self.owner_conn
8
+ else
9
+ PgConn.new(database, username)
10
+ end
100
11
  end
101
12
  end
data/prick.gemspec CHANGED
@@ -33,13 +33,13 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency "indented_io"
34
34
  spec.add_dependency "constrain"
35
35
  spec.add_dependency "forward_to"
36
- spec.add_dependency "bootsnap"
37
36
  spec.add_dependency "fixture_fox"
38
37
  spec.add_dependency "postspec"
39
38
  spec.add_dependency "pg_graph"
40
39
  spec.add_dependency "shellopts"
41
40
  spec.add_dependency "dsort"
42
41
  spec.add_dependency "string-text"
42
+ spec.add_dependency "prick-inflector" # Because we use it from inline-eval'ed scripts
43
43
 
44
44
  spec.add_development_dependency "ruby-prof"
45
45
 
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.39.12
4
+ version: 0.40.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-09-09 00:00:00.000000000 Z
11
+ date: 2024-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: bootsnap
70
+ name: fixture_fox
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: fixture_fox
84
+ name: postspec
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: postspec
98
+ name: pg_graph
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: pg_graph
112
+ name: shellopts
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -123,7 +123,7 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: shellopts
126
+ name: dsort
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: dsort
140
+ name: string-text
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
@@ -151,7 +151,7 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: string-text
154
+ name: prick-inflector
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - ">="
@@ -197,6 +197,7 @@ files:
197
197
  - doc/build-yml.txt
198
198
  - exe/prick
199
199
  - idea.txt
200
+ - lib/prick-command.rb
200
201
  - lib/prick.rb
201
202
  - lib/prick/builder/batch.rb
202
203
  - lib/prick/builder/builder.rb