prick 0.39.12 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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