prick 0.28.0 → 0.28.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '022876c913aadf3a2b42646f514f51c2db481c32db42a5903f2dbce88303d423'
4
- data.tar.gz: 1772f43a5a99fe93b97e748488f3c5bfa93fddce768176744ebb27e983cbb475
3
+ metadata.gz: 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