prick 0.33.0 → 0.34.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: c38cab1d940acbbaf8c36dadfddd5ce8a003e45fd881c0af033ee16b9b1bcb9b
4
- data.tar.gz: c9c67dd1b746adf191a2193c536a7c2a97a962f5a7b70c371ecf42bb60621cec
3
+ metadata.gz: 872c793b29eae6754796a66c93808a58507b5eac112ddadcf2260d7ed01f3435
4
+ data.tar.gz: 702ee64d93801ddc488664eeae903dc7a34b935585fd87d5c29299295c2e6a7d
5
5
  SHA512:
6
- metadata.gz: 7f81c8872d37f1b2f772c013c3fd1dad4f5936552aa8a6fa32b1b594d6da5ed20e06725590fd2a5317a8c9cda41b591845c6a68ec7ab4c2b82842b3fd2599c9c
7
- data.tar.gz: 17c08c190070820e63facc09a7411c8f90b43f7994a8a5eefc04baa2c7f0a9647ec562ce000d0677289d922f56c5eae410f68d0b323e8f177df122c1167b9fec
6
+ metadata.gz: 2524d9c7bbc4f73e5ecb343b5e6ef3caf47a9ed9541d18a96d458dd7024da9915d95b66a90e30dbdcc8e451b7d42cb19b1ae3682d001031ff8d5c09bedfcf354
7
+ data.tar.gz: a7359ccb249584edd6752509eaa90063d09029c7aa54f879e558644bb2ad2fa261975571f7945e9ea2726eacbdba8a85b7afb3ec8a46c57a0d0f1ec919e5b66f
data/TODO CHANGED
@@ -1,3 +1,4 @@
1
+ o make it possible to build only one scheme
1
2
  o cleanup connection
2
3
  o make environment optional
3
4
  o eval vs exit
data/exe/prick CHANGED
@@ -120,11 +120,9 @@ SPEC = %(
120
120
  TODO
121
121
 
122
122
  build! -f,force -t,time --dump=KIND? -- [SCHEMA]
123
- Build the project. If SCHEMA is defined, later schemas are excluded.
124
- KIND can be 'nodes', 'allnodes' or 'batches' (the default).
125
-
126
- Schemas marked with 'no refresh' is not built unless the --force is
127
- present
123
+ Build the project. If SCHEMA is defined, later schemas are excluded. KIND
124
+ can be 'nodes', 'allnodes' or 'batches' (the default). Schemas marked
125
+ with 'no refresh' is not built unless the --force is present
128
126
 
129
127
  make! -t,time --dump=KIND? -- [SCHEMA]
130
128
  @ Only rebuild changed files
@@ -133,6 +131,15 @@ SPEC = %(
133
131
  rebuild affected parts of the project. KIND can be 'nodes', 'allnodes' or
134
132
  'batches' (the default)
135
133
 
134
+ run! -t,time --dump=KIND? --schema=SCHEMA -- PATH
135
+ @ Execute path in Prick environment
136
+
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
140
+
141
+ The --schema option sets the current schema before executing the PATH
142
+
136
143
  bash! --main
137
144
  Emit a bash script to build the database. The script is constructed from
138
145
  the build attributes in the environment file
@@ -361,6 +368,13 @@ begin
361
368
  dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
362
369
  Prick::SubCommand.make(database, username, args.expect(0..1), timer: cmd.time?, dump: dump)
363
370
 
371
+ when :run!
372
+ require_db
373
+ dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
374
+ Prick::SubCommand.run(
375
+ database, username, args.expect(1),
376
+ timer: cmd.time?, dump: dump, schema: cmd.schema)
377
+
364
378
  when :bash!
365
379
  Prick::SubCommand.bash(main: cmd.main?)
366
380
 
@@ -27,9 +27,12 @@ module Prick
27
27
  # PgConn object
28
28
  attr_reader :conn
29
29
 
30
- # Root schema directory
30
+ # Schema directory
31
31
  attr_reader :dir
32
32
 
33
+ # Path to file or directory
34
+ attr_reader :path
35
+
33
36
  # Reflections YAML file
34
37
  attr_reader :reflections_file
35
38
 
@@ -39,6 +42,9 @@ module Prick
39
42
  # Root build node
40
43
  attr_reader :root
41
44
 
45
+ # True if this is a single-file build
46
+ attr_reader :single
47
+
42
48
  # Pool of nodes. Initialized by #load_pool
43
49
  attr_reader :pool
44
50
 
@@ -51,13 +57,17 @@ module Prick
51
57
 
52
58
  def batches() @batches ||= group end
53
59
 
54
- def initialize(conn, dir, clean = true, touched: false)
60
+ def initialize(conn, path, clean = true, single: false, touched: false)
61
+ File.exist?(path) or raise Error, "Can't find #{path}"
62
+ single || File.directory?(path) or raise Error, "Can't find directory #{path}"
63
+
55
64
  @conn = conn
56
- @dir = dir
65
+ @path = path
57
66
  @reflections_file = REFLECTIONS_PATH
58
67
  @clean = clean
68
+ @single = single
59
69
  @pool = NodePool.new
60
- @root = Parser.parse(conn, dir)
70
+ @root = Parser.parse(conn, path, single: single)
61
71
  load_pool(@root) # Collect nodes into pool
62
72
  @batches = nil # Initialized by #group
63
73
  end
@@ -110,7 +120,7 @@ module Prick
110
120
  Prick.state.save_build_begin
111
121
 
112
122
  # Create schemas
113
- conn.exec create_schemas.map { |schema| "create schema #{schema}" }
123
+ conn.exec create_schemas.map { |schema| "create schema #{schema}" } if !single
114
124
 
115
125
  # Execute batch groups
116
126
  t0 = Time.now
@@ -10,6 +10,8 @@ module Prick
10
10
 
11
11
  def name() @name = File.basename(path) end
12
12
 
13
+ # Note that schema defaults to 'public' which may not be what you want in
14
+ # all cases
13
15
  def schema() @schema ||= parent&.schema || "public" end
14
16
  def schema=(s) @schema = s end
15
17
 
@@ -36,6 +38,7 @@ module Prick
36
38
  constrain path, String, NilClass
37
39
  @parent, @phase, @kind, @path = parent, phase, kind, path
38
40
  @args = args&.empty? ? nil : args
41
+ @has_schema = false
39
42
  @schema = nil
40
43
  @source = nil
41
44
  @source_lines = nil
@@ -162,6 +165,9 @@ module Prick
162
165
 
163
166
  # A build.yml file node
164
167
  class BuildNode < Node
168
+ # True if the build file contains a 'schema' attribute
169
+ attr_accessor :has_schema
170
+
165
171
  def nodes() @nodes ||= init_nodes + decl_nodes + seed_nodes + term_nodes end
166
172
 
167
173
  attr_reader :decl_nodes
@@ -187,6 +193,7 @@ module Prick
187
193
  def dump
188
194
  puts "BuildNode #{path}"
189
195
  indent {
196
+ puts "has_schema: #{has_schema}"
190
197
  puts "schema: #{schema}" if schema
191
198
  puts "refresh: #{refresh_schema.to_s}"
192
199
  puts "pg_graph_ignore_schema: #{pg_graph_ignore_schema}"
@@ -1,25 +1,26 @@
1
1
  module Prick
2
2
  module Build
3
3
  class Parser
4
- def self.parse(conn, dir)
5
- Parser.new(conn).parse(dir).unit
6
- # [parser.unit, parser.schemas]
4
+ def self.parse(conn, path, single: true)
5
+ Parser.new(conn).parse(path, single: single).unit
7
6
  end
8
7
 
9
8
  attr_reader :conn
10
- attr_reader :dir
11
9
  attr_reader :unit # The singular RootBuildNode object
12
- # attr_reader :schemas
13
10
 
14
11
  def initialize(conn)
15
12
  @conn = conn
16
13
  @unit = nil
17
14
  end
18
15
 
19
- def parse(dir)
20
- @dir = dir
21
- # @schemas = {}
22
- parse_directory(nil, dir)
16
+ # Return a RootBuildNode object. #path can be a file or a directory
17
+ def parse(path, single: false)
18
+ File.exist?(path) or raise Error, "Can't find #{path}"
19
+ if single
20
+ parse_path(path)
21
+ else
22
+ parse_directory(nil, path)
23
+ end
23
24
  self
24
25
  end
25
26
 
@@ -35,36 +36,43 @@ module Prick
35
36
  end
36
37
 
37
38
  # Search for an executable in path. Return nil if not found
39
+ #
40
+ # Note that "." is ignored in the search path
38
41
  def find_executable(filename) # ChatGPT
39
42
  Prick.state.executable_search_path.split(File::PATH_SEPARATOR).each do |directory|
43
+ next if directory == "."
40
44
  path = File.join(directory, filename)
41
- return path if File.executable?(path)
45
+ return path if File.file?(path) && File.executable?(path)
42
46
  end
43
47
  nil
44
48
  end
45
49
 
46
- # Expand environments variables file name. Iterates through inherited
47
- # environments if the file name contain the $ENVIRONMENT variable. Return
48
- # nil if not found
50
+ # Expand environment variables in the given file name
51
+ #
52
+ # #expend_filename substitute '$<variable>' expressions in the filename
53
+ # with the corresponding value in the current environment. If the file
54
+ # was not found, inherited environments are processed hierarchly with the
55
+ # special environment variable ENVIRONMENT set to each PRICK_ENVIRONMENT
56
+ # value in the inherited environments
49
57
  #
50
- # The hierarchy of environments is defined in the PRICK_ENVIRONMENT_FILE
58
+ # Return the resulting path to the file and nil if not found
51
59
  #
52
60
  def expand_filename(dir, filename)
53
61
  envs = Prick.state.environments
54
62
  env = envs[Prick.state.environment]
55
- bash_vars = Prick.state.bash_environment
63
+ bash_vars = Prick.state.bash_environment.dup
56
64
 
57
65
  last = nil
58
66
  for env in [env] + env.ancestors.reverse
59
67
  bash_vars["ENVIRONMENT"] = env.name
60
68
  file = expand_variables(filename, bash_vars)
61
- last ||= (file != last and file) or return nil # return if no ENVIRONMENT substitution
69
+ # last ||= (file != last and file) or return nil # return if no ENVIRONMENT substitution
62
70
  path = (file.start_with?("/") ? file : File.join(dir, file))
63
71
 
64
72
  # Check for file (may be executable)
65
73
  return path if File.exist?(path)
66
74
 
67
- # Check for executable in search path
75
+ # Check for executable in search path if file doesn't contain a '/'
68
76
  path = find_executable(file) and return path if file !~ /\//
69
77
  end
70
78
 
@@ -77,12 +85,21 @@ module Prick
77
85
  expand_variables(string, Prick.state.bash_environment)
78
86
  end
79
87
 
88
+ def parse_path(path)
89
+ File.exist?(path) or raise Error, "Can't find #{file}"
90
+ dir = File.dirname(path)
91
+ file = File.basename(path)
92
+ unit = make_build_unit(nil, nil)
93
+ parse_build_entry(unit, dir, file)
94
+ unit
95
+ end
96
+
80
97
  def parse_directory(parent, dir)
81
98
  build_file = "#{dir}/build.yml".sub(/\/\//, "/")
82
99
  if File.exist? build_file
83
100
  parse_build_file(parent, dir, build_file)
84
101
  else
85
- raise Error, "Can't find build.yml in #{dir}"
102
+ raise Error, "Can't find build.yml in #{dir} while parsing #{parent}"
86
103
  end
87
104
  end
88
105
 
@@ -94,6 +111,7 @@ module Prick
94
111
  entry.each { |key, value|
95
112
  if key == "schema"
96
113
  unit.schema = value
114
+ unit.has_schema = true
97
115
  elsif key == "standard"
98
116
  unit.pg_graph_ignore_schema = !value
99
117
  elsif key == "refresh"
@@ -109,17 +127,22 @@ module Prick
109
127
  end
110
128
  }
111
129
  else
112
- node = parse_entry(unit, :decl, dir, entry) or next
113
- if node.kind == :fox
114
- unit.seed_nodes << node
115
- else
116
- unit.decl_nodes << node
117
- end
130
+ parse_build_entry(unit, dir, entry)
118
131
  end
119
132
  }
120
133
  unit
121
134
  end
122
135
 
136
+ def parse_build_entry(unit, dir, file)
137
+ node = parse_entry(unit, :decl, dir, file) or return nil
138
+ if node.kind == :fox
139
+ unit.seed_nodes << node
140
+ else
141
+ unit.decl_nodes << node
142
+ end
143
+ node
144
+ end
145
+
123
146
  # Returns path, filename, and an array of arguments. It is an error if
124
147
  # the file can't be found unless #optional is true. In that case a nil
125
148
  # value is returned
@@ -132,7 +155,7 @@ module Prick
132
155
  args = expand_string(rest || '').split
133
156
  path = expand_filename(dir, command)
134
157
  path || optional or
135
- raise Error, "Can't find file\n #{command}\n #{path}\n in #{dir}/\n from #{unit}"
158
+ raise Error, "Can't find file #{command} #{path} in #{dir}/ from #{unit}"
136
159
  !path.nil? or return nil
137
160
  else
138
161
  raise Error, "Not a file name: '#{entry}'"
@@ -141,6 +164,7 @@ module Prick
141
164
  end
142
165
 
143
166
  def parse_entry(unit, phase, dir, entry)
167
+ # puts "#parse_entry(#{unit.inspect}, #{phase.inspect}, #{dir.inspect}, #{entry.inspect})"
144
168
  if entry.is_a?(Hash)
145
169
  entry.size == 1 or raise Error, "sql and module are single-line values"
146
170
  key, value = entry.first
@@ -175,7 +199,9 @@ module Prick
175
199
  SqlNode.new(unit, phase, path)
176
200
  when /\.fox$/
177
201
  FoxNode.new(unit, :seed, path)
178
- when /build-.*\.yml$/
202
+ when /(?:^|\/)build-.*\.yml$/
203
+ parse_build_file(unit, dir, path)
204
+ when /(?:^|\/)build.yml$/ # Only used when building a single file
179
205
  parse_build_file(unit, dir, path)
180
206
  else
181
207
  raise Error, "Expected executable, fox, or sql file: #{File.basename(path)} in #{dir}"
@@ -245,13 +245,12 @@ module Prick
245
245
  case types[ident]
246
246
  when "BOOLEAN"
247
247
  [TrueClass, FalseClass].include?(value.class) or raise "Illegal value for #{ident}: #{value}"
248
- ;
249
248
  when "STRING"
250
249
  ; # nop
251
250
  when "LIST"
252
251
  value = value&.split || []
253
252
  when "TEXT"
254
- value = value.chomp
253
+ value = (value == false ? "false" : value.chomp)
255
254
  when nil
256
255
  ShellOpts.error "Unknown variable '#{ident}'"
257
256
  else
data/lib/prick/state.rb CHANGED
@@ -14,6 +14,9 @@ module Prick
14
14
  # directory through the -C option or the 'init' command
15
15
  def prick_dir() @prick_dir ||= Dir.getwd end
16
16
 
17
+ # Prick schema dir
18
+ def schema_dir() @schema_dir ||= File.join(prick_dir, SCHEMA_DIR) end
19
+
17
20
  # Project file. Default 'prick.yml'
18
21
  attr_reader :project_file
19
22
 
@@ -186,7 +189,7 @@ module Prick
186
189
  hash.merge!({
187
190
  "DATABASE" => Prick.state.database, # FIXME: Yt
188
191
  "USERNAME" => Prick.state.username, # FIXME: Yt
189
- "ENVIRONMENT" => Prick.state.environment.to_s, # FIXME: Yt
192
+ "ENVIRONMENT" => Prick.state.environment.to_s, # FIXME: Yt except in build.yml parser
190
193
  "PRICK_NAME" => Prick.state.name,
191
194
  "PRICK_TITLE" => Prick.state.title,
192
195
  "PRICK_VERSION" => Prick.state.version,
@@ -5,7 +5,8 @@ require_relative '../builder/builder.rb'
5
5
  module Prick::SubCommand
6
6
  def self.build(
7
7
  database, username, schema,
8
- builddir: "schema", force: false,
8
+ builddir: Prick.state.schema_dir,
9
+ force: false, # Build all schemas
9
10
  timer: nil, dump: nil)
10
11
 
11
12
  Timer.on! if timer
@@ -49,8 +50,8 @@ module Prick::SubCommand
49
50
  # Remove keep-schemas from list of schemas
50
51
  refresh_schemas -= keep_schemas
51
52
 
52
- # Also remove keep-schemas from the build pool. Why don't we use
53
- # the pool tracker's list of keep schemas?
53
+ # Also remove keep-schemas from the build pool. TODO Why don't we
54
+ # use the pool tracker's list of keep schemas?
54
55
  builder.pool.delete_schema(keep_schemas)
55
56
 
56
57
  # Drop refresh schemes
@@ -5,7 +5,7 @@ require_relative '../builder/builder.rb'
5
5
  module Prick::SubCommand
6
6
  def self.make(database, username, schema, timer: nil, dump: nil)
7
7
  Timer.on! if timer
8
- time "Prick::Command#make" do
8
+ Timer.time "Prick::Command#make" do
9
9
  begin
10
10
  super_conn = State.connection
11
11
  conn = nil
@@ -14,7 +14,7 @@ module Prick::SubCommand
14
14
  clean = false
15
15
  create_schemas = []
16
16
 
17
- time "Load build object" do
17
+ Timer.time "Load build object" do
18
18
  if super_conn.rdbms.exist? database
19
19
  conn = Prick.state.connection
20
20
  if conn.schema.exist_relation?("prick", "versions") && !conn.empty?("prick.versions")
@@ -43,7 +43,7 @@ module Prick::SubCommand
43
43
  after_schemas.each { |delete_schema| builder.pool.delete_schema(delete_schema) }
44
44
  end
45
45
 
46
- touched_nodes = builder.nodes.select { |node| File.mtime(node.path) > last_built_at }
46
+ touched_nodes = builder.nodes.select { |node| last_built_at.nil? || File.mtime(node.path) > last_built_at }
47
47
  touched_phases = touched_nodes.map(&:phase).uniq.compact
48
48
  touched_kinds = touched_nodes.map(&:kind).uniq.compact
49
49
  touched_schema = touched_nodes.first&.schema
@@ -78,7 +78,7 @@ module Prick::SubCommand
78
78
  end
79
79
  exit if dump
80
80
 
81
- time "Execute build object" do
81
+ Timer.time "Execute build object" do
82
82
  builder.execute(conn, create_schemas: create_schemas)
83
83
  end
84
84
 
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../builder/builder.rb'
4
+
5
+ module Prick::SubCommand
6
+ def self.run(database, username, path, timer: nil, dump: nil, schema: nil)
7
+
8
+ Timer.on! if timer
9
+
10
+ Timer.time "Prick::Command#build" do
11
+ begin
12
+ super_conn = State.connection # Used to create new databases (doesn't make a
13
+ # difference right now as the database user is
14
+ # a superuser anyway
15
+ conn = nil
16
+ builder = nil
17
+
18
+ constrain super_conn.rdbms.exist?(database), true # FIXME Same problem as below. Also in other commands
19
+
20
+ Timer.time "Load build object" do
21
+ if super_conn.rdbms.exist? database # FIXME Why create database? Setup should have done this
22
+ exist = true
23
+ else
24
+ super_conn.rdbms.create database, owner: username
25
+ exist = false
26
+ end
27
+ conn = Prick.state.connection
28
+
29
+ # Parse
30
+ builder = Prick::Build::Builder.new(conn, path, single: true)
31
+
32
+ # Register if a build file is referenced and normalize path to
33
+ # include build.yml of directories
34
+ if File.directory?(path)
35
+ path = File.join(path, "build.yml")
36
+ is_build_file = true
37
+ elsif File.basename(path) == "build.yml"
38
+ is_build_file = true
39
+ else
40
+ is_build_file = false
41
+ end
42
+
43
+ # Read schema from build file if possible and decide if schema should
44
+ # be dropped beforehand
45
+ if is_build_file
46
+ pool = builder.pool
47
+ build_node = pool.all_nodes.first
48
+ if build_node.has_schema
49
+ !schema or Prick.error "Can't use --schema when doing a schema build"
50
+ is_schema_rebuild = true
51
+ schema = build_node.schema
52
+ else
53
+ is_schema_rebuild = false
54
+ end
55
+ else
56
+ is_schema_rebuild = false
57
+ end
58
+
59
+ # Infer schema from path
60
+ if !schema
61
+ abspath = File.realpath(path)
62
+ schemapath = File.realpath(Prick.state.schema_dir)
63
+ if abspath =~ /^#{schemapath}\/([^\/]+)(?:\/.*)$/
64
+ schema = $1
65
+ else
66
+ Prick.error "Can't find schema" # No default schema to avoid unintended runs
67
+ end
68
+ end
69
+
70
+ # Drop schema if needed
71
+ if is_schema_rebuild
72
+ conn.schema.drop schema, cascade: true
73
+ end
74
+
75
+ # Create schema if absent
76
+ if !conn.schema.exist?(schema)
77
+ conn.schema.create(schema)
78
+ end
79
+ end
80
+
81
+ case dump
82
+ when :nodes; builder.nodes.reject { |node| node.is_a?(Build::BuildNode) }.map &:dump
83
+ when :allnodes; builder.nodes.map &:dump
84
+ when :batches; builder.dump
85
+ when nil;
86
+ else
87
+ Prick.error "Illegal dump type: #{dump.inspect}"
88
+ end && exit
89
+
90
+ Timer.time "Execute build object" do
91
+ builder.execute conn
92
+ end
93
+
94
+ rescue Prick::Error => ex
95
+ $stderr.puts ex.message
96
+ exit 1
97
+
98
+ rescue ::Command::Error => ex
99
+ $stderr.puts ex.message
100
+ exit 1
101
+
102
+ ensure
103
+ super_conn&.terminate
104
+ conn&.terminate
105
+ end
106
+ end
107
+ end
108
+ end
109
+
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.33.0"
4
+ VERSION = "0.34.0"
5
5
  end
data/lib/prick.rb CHANGED
@@ -50,6 +50,7 @@ require_relative 'prick/subcommand/prick-list.rb'
50
50
  require_relative 'prick/subcommand/prick-make.rb'
51
51
  require_relative 'prick/subcommand/prick-migrate.rb'
52
52
  require_relative 'prick/subcommand/prick-release.rb'
53
+ require_relative 'prick/subcommand/prick-run.rb'
53
54
  require_relative 'prick/subcommand/prick-set.rb'
54
55
  require_relative 'prick/subcommand/prick-setup.rb'
55
56
  require_relative 'prick/subcommand/prick-teardown.rb'
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.33.0
4
+ version: 0.34.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-04-23 00:00:00.000000000 Z
11
+ date: 2024-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic
@@ -230,6 +230,7 @@ files:
230
230
  - lib/prick/subcommand/prick-make.rb
231
231
  - lib/prick/subcommand/prick-migrate.rb
232
232
  - lib/prick/subcommand/prick-release.rb
233
+ - lib/prick/subcommand/prick-run.rb
233
234
  - lib/prick/subcommand/prick-set.rb
234
235
  - lib/prick/subcommand/prick-setup.rb
235
236
  - lib/prick/subcommand/prick-teardown.rb