prick 0.28.0 → 0.28.1

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: '022876c913aadf3a2b42646f514f51c2db481c32db42a5903f2dbce88303d423'
4
- data.tar.gz: 1772f43a5a99fe93b97e748488f3c5bfa93fddce768176744ebb27e983cbb475
3
+ metadata.gz: 10bf0db9a65cd8441d8f17ea9ad055f177d49508f2445ee733722fcc0fb5c6dc
4
+ data.tar.gz: 693a602c5f57b6bdd97cdefa00c69a0c517cd54e4b32e2d10657fa1c8cfb8949
5
5
  SHA512:
6
- metadata.gz: d8c647cc168aee7f0aab47d39d6ce1050331a06b6aa7a04fc8173152618432c377eb7ce42784eb95d2a70134be10bddd0ff3a43298c2e20af8a3649dc8227451
7
- data.tar.gz: 84068d08a68d2b1177054faa4d31218bf7bc66cf437bf255a6ff2404c0126e10288e58519e11fe7c80029c7169ff9e3a59be5327c39f8fd112bc0a846519d833
6
+ metadata.gz: 1ec3e792b312d572d9eb65feb3c595d94ec944b9854f12c9f16a805dfce24a45d0be6fe923c02fc1bf57c9a3ce0d6b221c28eaeaea5a1ab1a0f044e228a07b5e
7
+ data.tar.gz: a0cc6eb39133fbfc7eef170a173378bd685a3f43315ac633748dc1fafa02589dae100d1fe8f30ab4687a175e3d8a13d0fca793245fbb10fb2297fed5d8e4c659
data/TODO CHANGED
@@ -1,3 +1,4 @@
1
+ o eval vs exit
1
2
  o Make it possible to define (nested) environments
2
3
  o Use standard directory layout
3
4
 
data/exe/prick CHANGED
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- #$LOAD_PATH.unshift "/home/clr/prj/shellopts/lib"
4
- #$LOAD_PATH.unshift "/home/clr/prj/pg_graph/lib"
5
- #$LOAD_PATH.unshift "/home/clr/prj/fixture_fox/lib"
6
-
7
3
  require 'bootsnap'
8
4
  begin
9
5
  cache_dir = "/run/user/" + `id -u`.chomp
data/lib/builder/batch.rb CHANGED
@@ -32,8 +32,8 @@ module Prick
32
32
  super {
33
33
  begin
34
34
  sql = []
35
- # A SQL batch allows the first node to be an executable
36
- if nodes.first.is_a?(ExeNode)
35
+ # A SQL batch allows the first node to be an evaluated or executed node
36
+ if nodes.first.is_a?(CommandNode)
37
37
  time "Execute script" do
38
38
  sql = [nodes.first.source]
39
39
  end
data/lib/builder/node.rb CHANGED
@@ -41,7 +41,7 @@ module Prick
41
41
  @source_lines = nil
42
42
  end
43
43
 
44
- def to_s() [path, args].compact.join(" ") end
44
+ def to_s() @to_s ||= [path, args].compact.join(" ") end
45
45
  def inspect() to_s end
46
46
  def dump() puts "#{inspect} (#{schema})" end
47
47
 
@@ -121,41 +121,45 @@ module Prick
121
121
  def self.objects() @@objects end
122
122
  end
123
123
 
124
- class ExeNode < Node
125
- # Using a pipe instead of just executing the command shaves off some
126
- # deciseconds spent starting up bash. It expects the process to read
127
- # database/username from standard input
128
- attr_reader :pipe
124
+ class CommandNode < Node
125
+ alias_method :command, :to_s
126
+
127
+ def filename = File.basename(path)
128
+ def relpath = path.sub(/^#{Dir.getwd}\//, "")
129
129
 
130
130
  def initialize(parent, phase, path, args = nil)
131
+ constrain args, [String]
131
132
  super(parent, phase, :exe, path, args)
132
- @pipe = Command::Pipe.new(to_s, stderr: nil)
133
133
  end
134
134
 
135
135
  def inspect() "#{path} #{(args || []).join(" ")}" end
136
136
 
137
137
  protected
138
- def read_source
138
+ def execute_command
139
139
  begin
140
- pipe.puts [conn.name, conn.user]
141
- sql = pipe.wait
140
+ Command.command(Prick.state.bash_environment, command)
142
141
  rescue Command::Error => ex
143
- message = "prick: Error executing '#{to_s}'\n" + ex.stderr.map { |l| " #{l}" }.join("\n")
142
+ message = "Error executing '#{command}'\n" + ex.stderr.map { |l| " #{l}" }.join("\n")
144
143
  raise Prick::Error.new(message)
145
-
146
- rescue Errno::EPIPE
147
- raise Prick::Error, "Pipe fail in #{path} called from #{parent.path}\n" +
148
- "Tip: Read username/database from standard input"
149
- end
150
- if pipe.status != 0
151
- $stderr.puts pipe.error
152
- raise Prick::Error, "Failed executing #{path} called from #{parent.path}"
153
144
  end
145
+ end
146
+ end
147
+
148
+ class EvalNode < CommandNode
149
+ def read_source
150
+ sql = execute_command
154
151
  @source_lines = 1 + 1 + sql.count("\n")
155
152
  ["set search_path to #{schema}, pg_temp;\n", sql]
156
153
  end
157
154
  end
158
155
 
156
+ class ExecNode < CommandNode
157
+ def read_source
158
+ execute_command
159
+ []
160
+ end
161
+ end
162
+
159
163
  # A build.yml file node
160
164
  class BuildNode < Node
161
165
  def nodes() @nodes ||= init_nodes + decl_nodes + seed_nodes + term_nodes end
@@ -34,6 +34,91 @@ module Prick
34
34
  end
35
35
  end
36
36
 
37
+ # Search for an executable in path
38
+ def find_executable(filename) # ChatGPT
39
+ Prick.state.executable_search_path.split(File::PATH_SEPARATOR).each do |directory|
40
+ path = File.join(directory, filename)
41
+ return path if File.executable?(path)
42
+ end
43
+ nil
44
+ end
45
+
46
+ # Expand $ENVIRONMENT variable in file names. The function implements a
47
+ # hierarchy of environments:
48
+ #
49
+ # production
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
74
+ #
75
+ def expand_filename(dir, filename)
76
+ # Expand variables
77
+ path = expand_variables(filename, Prick.state.bash_environment)
78
+
79
+ # Use only filename as template if expansion is an absolute path
80
+ if path.start_with? "/"
81
+ template = filename
82
+ else
83
+ template = "#{dir}/#{filename}"
84
+ path = "#{dir}/#{path}"
85
+ end
86
+ return path if File.exist?(path)
87
+
88
+ # Iterate through environments
89
+ env = Prick.state.environment.to_s
90
+ while true
91
+ 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
+
109
+ # Look for executable in PATH
110
+ if filename !~ /\// && (path = find_executable(filename))
111
+ return path
112
+ else
113
+ return nil
114
+ end
115
+ end
116
+
117
+ # Expand $ENVIRONMENT variable
118
+ def expand_string(string)
119
+ expand_variables(string, ENVIRONMENT: Prick.state.environment.to_s, PWD: Dir.getwd)
120
+ end
121
+
37
122
  def parse_directory(parent, dir)
38
123
  build_file = "#{dir}/build.yml".sub(/\/\//, "/")
39
124
  if File.exist? build_file
@@ -77,89 +162,24 @@ module Prick
77
162
  unit
78
163
  end
79
164
 
80
- # Expand $ENVIRONMENT variable in file names. The function implements a
81
- # hierarchy of environments:
82
- #
83
- # production
84
- # (online)
85
- # development
86
- # online
87
- # offline
88
- # backend
89
- # frontend
90
- # test
91
- #
92
- # If an environment doesn't exist in #dir, then the higher level
93
- # environments are tried in turn. Eg. if the current environment is
94
- # 'test', the algorithm expands '$ENVIRONMENT' to 'test', 'offline', and
95
- # 'development' until an existing file is found. If no file is found the
96
- # the unexpanded value is returned
97
- #
98
- # NOTE: This is a hardcoded feature for an internal project. It is
99
- # configurable in the next major version
100
- #
101
- def expand_filename(dir, filename)
102
- if File.exist?("#{dir}/#{filename}")
103
- return filename
104
- elsif filename =~ /\$ENVIRONMENT|\$\{ENVIRONMENT\}/
105
- env = Prick.state.environment.to_s
106
- while true
107
- file = expand_variables(filename, ENVIRONMENT: env, PWD: Dir.getwd)
108
- return file if File.exist? "#{dir}/#{file}"
109
- case env
110
- when "production"
111
- file = expand_variables(filename, ENVIRONMENT: "online", PWD: Dir.getwd) or return nil
112
- return file if File.exist? "#{dir}/#{file}"
113
- return nil
114
- when "development"; return nil
115
- when "online"; env = "development"
116
- when "offline"; env = "development"
117
- when "backend"; env = "offline"
118
- when "frontend"; env = "offline"
119
- when "test"; env = "offline"
120
- else
121
- raise Error, "Illegal env: '#{env}'"
122
- end
123
- end
124
- else
125
- return nil
126
- end
127
- end
128
-
129
- # Expand $ENVIRONMENT variable
130
- def expand_string(string)
131
- expand_variables(string, ENVIRONMENT: Prick.state.environment.to_s, PWD: Dir.getwd)
132
- end
133
-
165
+ # Returns path, filename, and an array of arguments. It is an error if
166
+ # the file can't be found unless #optional is true. In that case a nil
167
+ # value is returned
134
168
  def parse_file_entry(unit, dir, entry)
135
169
  entry = entry.sub(/\/$/, "")
136
170
  if entry =~ /^(\S+?)(\?)?(?:\s+(.+))?\s*$/
137
- filename = $1
171
+ command = $1
138
172
  optional = !$2.nil?
139
173
  args = expand_string($3 || '').split
140
- file = expand_filename(dir, filename)
141
- file || optional or raise Error, "Can't find #{filename} in #{dir} from #{unit}"
142
- !file.nil? or return nil
174
+ path = expand_filename(dir, command)
175
+ path || optional or raise Error, "Can't find '#{entry}' in #{dir}/ from #{unit}"
176
+ !path.nil? or return nil
143
177
  else
144
178
  raise Error, "Not a file name: '#{entry}'"
145
179
  end
146
- path = "#{dir}/#{file}"
147
- [path, entry, file, args].flatten
180
+ [path, Array(args).flatten]
148
181
  end
149
182
 
150
- # def parse_file_entry(dir, entry)
151
- # name = expand_environment(dir, entry)
152
- # name.sub!(/\/$/, "")
153
- # if name =~ /^(\S+)\s+(.+)$/ # has arguments -> exe
154
- # file = $1
155
- # args = $2.split
156
- # else
157
- # file = name
158
- # end
159
- # path = "#{dir}/#{file}"
160
- # [path, name, file, args]
161
- # end
162
-
163
183
  def parse_entry(unit, phase, dir, entry)
164
184
  if entry.is_a?(Hash)
165
185
  entry.size == 1 or raise Error, "sql and module are single-line values"
@@ -168,19 +188,27 @@ module Prick
168
188
  when "sql"
169
189
  InlineNode.new(unit, phase, unit.path, expand_string(value))
170
190
  when "call"
171
- (path, name, file, klass, command, args = parse_file_entry(unit, dir, value)) or return nil
191
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
192
+ args.size >= 2 or raise Error, "Illegal number of arguments"
193
+ klass = args.shift
194
+ command = args.shift
172
195
  klass && command or raise "Illegal number of arguments: #{value}"
173
- ModuleNode.new(unit, phase, "#{dir}/#{file}", klass, command, args)
196
+ ModuleNode.new(unit, phase, path, klass, command, args)
197
+ when "eval"
198
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
199
+ EvalNode.new(unit, phase, path, args)
174
200
  when "exec"
175
- (path, name, file, args = parse_file_entry(unit, dir, value)) or return nil
176
- ExeNode.new(unit, phase, path, args)
201
+ (path, args = parse_file_entry(unit, dir, value)) or return nil
202
+ ExecNode.new(unit, phase, path, args)
177
203
  else
178
204
  raise Error, "Illegal key: #{key}"
179
205
  end
180
206
  else
181
- (path, name, file, args = parse_file_entry(unit, dir, entry)) or return nil
207
+ (path, args = parse_file_entry(unit, dir, entry)) or return nil
182
208
  if File.directory? path
183
209
  parse_directory(unit, path)
210
+ elsif File.executable? path
211
+ ExecNode.new(unit, phase, path, args)
184
212
  elsif File.file? path
185
213
  case path
186
214
  when /\.sql$/
@@ -188,14 +216,11 @@ module Prick
188
216
  when /\.fox$/
189
217
  FoxNode.new(unit, :seed, path)
190
218
  else
191
- if File.executable? path
192
- ExeNode.new(unit, phase, path, args)
193
- else
194
- raise Error, "Unrecognized file type #{File.basename(path)} in #{dir}"
195
- end
219
+ raise Error, "Expected executable, fox, or sql file: #{File.basename(path)} in #{dir}"
196
220
  end
197
221
  else
198
- raise Error, "Can't find #{name} in #{dir} from #{unit}"
222
+ path =
223
+ raise Error, "Can't find '#{entry}' in #{dir}/ from #{unit}"
199
224
  end
200
225
  end
201
226
  end
data/lib/local/command.rb CHANGED
@@ -97,16 +97,15 @@ module Command
97
97
  # command on standard-input, if it is a IO object that IO object is piped to
98
98
  # the command
99
99
  #
100
- # By default #command pass through standard-error but if :stderr is true,
101
- # #command will return a tuple of standard-output, standard-error lines. If
102
- # :stderr is false, standard-error is ignored and is the same as adding
103
- # "2>/dev/null" to the command
100
+ # By default #command pass through stderr but if :stderr is true, #command
101
+ # will instead return a tuple of stdout/stderr lines. If :stderr is false,
102
+ # stderr is ignored and is the same as adding "2>/dev/null" to the command
104
103
  #
105
- # #command raises a Command::Error exception if the command return with an
106
- # exit code != 0 unless :fail is false. In that case the the exit code can be
104
+ # #command raises a Command::Error exception if the command returns with an exit
105
+ # code != 0 unless :fail is false. In that case the the exit code can be
107
106
  # fetched from Command::status
108
107
  #
109
- def command(cmd, stdin: nil, stderr: nil, fail: true)
108
+ def command(env = {}, cmd, stdin: nil, stderr: nil, fail: true)
110
109
  cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
111
110
 
112
111
  pw = IO::pipe # pipe[0] for read, pipe[1] for write
@@ -129,7 +128,8 @@ module Command
129
128
  STDERR.reopen(pe[1])
130
129
  pe[1].close
131
130
 
132
- exec(cmd)
131
+ env
132
+ exec(env.map { |k,v| [k.to_s, v.to_s] }.to_h, cmd)
133
133
  }
134
134
 
135
135
  pw[0].close
@@ -143,15 +143,14 @@ module Command
143
143
  when Array; pw[1].write(stdin.join("\n") + "\n")
144
144
  end
145
145
  pw[1].flush
146
- pw[1].close
147
146
  end
147
+ pw[1].close # Closing standard input so the command doesn't hang on read
148
148
 
149
149
  @status = Process.waitpid2(pid)[1].exitstatus
150
150
 
151
151
  out = pr[0].readlines.map(&:chomp)
152
152
  err = pe[0].readlines.map(&:chomp)
153
153
 
154
- pw[1].close if !stdin
155
154
  pr[0].close
156
155
  pe[0].close
157
156
 
@@ -171,13 +170,6 @@ module Command
171
170
  end
172
171
  end
173
172
 
174
- # Exit status of the last command. FIXME: This is not usable because the
175
- # methods raise an exception if the command exited with anything but 0
176
- def status() @status end
177
-
178
- # Stored exception when #command is called with :fail true
179
- def exception() @exception end
180
-
181
173
  # Like command but returns true if the command exited with the expected
182
174
  # status. Note that it suppresses standard-error by default
183
175
  #
@@ -186,6 +178,12 @@ module Command
186
178
  @status == expect
187
179
  end
188
180
 
181
+ # Exit status of the last command
182
+ def status() @status end
183
+
184
+ # Exception of the last command if it failed, otherwise nil TODO What is this used for?
185
+ def exception() @exception end
186
+
189
187
  module_function :command
190
188
  module_function :status
191
189
  module_function :exception
@@ -14,8 +14,10 @@ module Prick
14
14
  DIRS = [
15
15
  MIGRATION_DIR = "migration",
16
16
  SCHEMA_DIR = "schema",
17
- PRICK_DIR = "#{SCHEMA_DIR}/prick",
17
+ SCHEMA_PRICK_DIR = "#{SCHEMA_DIR}/prick",
18
18
  PUBLIC_DIR = "#{SCHEMA_DIR}/public",
19
+ BIN_DIR = "bin",
20
+ LIBEXEC_DIR = "libexec",
19
21
  VAR_DIR = "var",
20
22
  CACHE_DIR = "#{VAR_DIR}/cache",
21
23
  SPOOL_DIR = "#{VAR_DIR}/spool",
@@ -24,6 +26,12 @@ module Prick
24
26
  SPEC_DIR = "spec"
25
27
  ]
26
28
 
29
+ # Prick project root directory
30
+ PRICK_DIR = Dir.getwd
31
+
32
+ # Search path for executables
33
+ PRICK_PATH = "#{ENV['PATH']}:#{PRICK_DIR}/#{BIN_DIR}:#{PRICK_DIR}/#{LIBEXEC_DIR}"
34
+
27
35
  # Project specification file
28
36
  PRICK_PROJECT_FILE = "prick.yml"
29
37
  PRICK_PROJECT_PATH = PRICK_PROJECT_FILE
@@ -42,7 +50,7 @@ module Prick
42
50
 
43
51
  # Schema data file
44
52
  SCHEMA_VERSION_FILE = "data.sql"
45
- SCHEMA_VERSION_PATH = File.join(PRICK_DIR, SCHEMA_VERSION_FILE)
53
+ SCHEMA_VERSION_PATH = File.join(SCHEMA_PRICK_DIR, SCHEMA_VERSION_FILE)
46
54
 
47
55
  # Rspec temporary directory
48
56
  SPEC_TMP_DIR = "spec"
data/lib/prick/state.rb CHANGED
@@ -36,6 +36,35 @@ module Prick
36
36
  # Name of database user
37
37
  attr_accessor :username
38
38
 
39
+ # Prick project dir. This is not a constant because exe/prick can change directory
40
+ def prick_dir
41
+ @prick_dir ||= Dir.getwd
42
+ end
43
+
44
+ # Prick executable search_path
45
+ def executable_search_path
46
+ @executable_search_path ||= "#{ENV['PATH']}:#{prick_dir}/#{BIN_DIR}:#{prick_dir}/#{LIBEXEC_DIR}"
47
+ end
48
+
49
+ def bash_environment
50
+ ENV.to_h.merge({
51
+ DATABASE: Prick.state.database,
52
+ USERNAME: Prick.state.username,
53
+ ENVIRONMENT: Prick.state.environment.to_s,
54
+ PRICK_DIR: Prick.state.prick_dir,
55
+ PRICK_SCHEMADIR: File.join(Prick.state.prick_dir, SCHEMA_DIR),
56
+ PRICK_BINDIR: File.join(Prick.state.prick_dir, BIN_DIR),
57
+ PRICK_LIBEXECDIR: File.join(Prick.state.prick_dir, LIBEXEC_DIR),
58
+ PRICK_VARDIR: File.join(Prick.state.prick_dir, VAR_DIR),
59
+ PRICK_CACHEDIR: File.join(Prick.state.prick_dir, CACHE_DIR),
60
+ PRICK_SPOOLDIR: File.join(Prick.state.prick_dir, SPOOL_DIR),
61
+ PRICK_TMPDIR: File.join(Prick.state.prick_dir, TMP_DIR),
62
+ PRICK_CLONEDIR: File.join(Prick.state.prick_dir, CLONE_DIR),
63
+ PRICK_SPECDIR: File.join(Prick.state.prick_dir, BIN_DIR),
64
+ PATH: Prick.state.executable_search_path
65
+ })
66
+ end
67
+
39
68
  def self.load
40
69
  begin
41
70
  h = YAML.load(File.read PRICK_PROJECT_PATH)
data/lib/prick/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prick
4
- VERSION = "0.28.0"
4
+ VERSION = "0.28.1"
5
5
  end
@@ -29,6 +29,7 @@ module Prick::SubCommand
29
29
  if force
30
30
  # Drop all schemas but re-creates the public schema
31
31
  super_conn.rdbms.empty!(database)
32
+ conn.schema.drop("public") # drop it again FIXME
32
33
 
33
34
  else
34
35
  # Find schemas to refresh. This includes all schemas in the
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.28.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-08 00:00:00.000000000 Z
11
+ date: 2024-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic
@@ -231,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
231
  - !ruby/object:Gem::Version
232
232
  version: '0'
233
233
  requirements: []
234
- rubygems_version: 3.3.7
234
+ rubygems_version: 3.3.18
235
235
  signing_key:
236
236
  specification_version: 4
237
237
  summary: A release control and management system for postgresql