prick 0.31.0 → 0.33.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: 71425ed90edd4de504fc95a6d270a5659c0ff206710db81c4994b03af76931e9
4
- data.tar.gz: 3c5454cc8052d994257dba637285088352196f7bb522dd16fb19e4d43cafe3b8
3
+ metadata.gz: c38cab1d940acbbaf8c36dadfddd5ce8a003e45fd881c0af033ee16b9b1bcb9b
4
+ data.tar.gz: c9c67dd1b746adf191a2193c536a7c2a97a962f5a7b70c371ecf42bb60621cec
5
5
  SHA512:
6
- metadata.gz: f59c9a5ac1284eeb006327370308630ec24b3dc30dfa9af10f0695f6b0501bcfe207750b78893315f2478364eee576f780a65662d6827aa77fe2b654f5b38a8a
7
- data.tar.gz: 7958f8d49c73c5c1b2de604fb671367c2704ef3d373c0847ae9861378f719b2e6e9d02661c8b3c36ce91541e8adb71875f3d233f7151277c64d51439087203ad
6
+ metadata.gz: 7f81c8872d37f1b2f772c013c3fd1dad4f5936552aa8a6fa32b1b594d6da5ed20e06725590fd2a5317a8c9cda41b591845c6a68ec7ab4c2b82842b3fd2599c9c
7
+ data.tar.gz: 17c08c190070820e63facc09a7411c8f90b43f7994a8a5eefc04baa2c7f0a9647ec562ce000d0677289d922f56c5eae410f68d0b323e8f177df122c1167b9fec
data/exe/prick CHANGED
@@ -121,10 +121,10 @@ SPEC = %(
121
121
 
122
122
  build! -f,force -t,time --dump=KIND? -- [SCHEMA]
123
123
  Build the project. If SCHEMA is defined, later schemas are excluded.
124
- KIND can be 'nodes', 'allnodes' or 'batches' (the default)
124
+ KIND can be 'nodes', 'allnodes' or 'batches' (the default).
125
125
 
126
- Usually, schemas marked with 'no refresh' is not built, use --force to
127
- rebuild all schemas
126
+ Schemas marked with 'no refresh' is not built unless the --force is
127
+ present
128
128
 
129
129
  make! -t,time --dump=KIND? -- [SCHEMA]
130
130
  @ Only rebuild changed files
@@ -133,6 +133,10 @@ SPEC = %(
133
133
  rebuild affected parts of the project. KIND can be 'nodes', 'allnodes' or
134
134
  'batches' (the default)
135
135
 
136
+ bash! --main
137
+ Emit a bash script to build the database. The script is constructed from
138
+ the build attributes in the environment file
139
+
136
140
  fox! -- FILE...
137
141
  Load fox file data. Data are reset to their initial state after build
138
142
  before the fox data are loaded. This makes it possible to experiment with
@@ -357,6 +361,9 @@ begin
357
361
  dump = cmd.dump? ? cmd.dump("batches")&.to_sym || :batches : nil
358
362
  Prick::SubCommand.make(database, username, args.expect(0..1), timer: cmd.time?, dump: dump)
359
363
 
364
+ when :bash!
365
+ Prick::SubCommand.bash(main: cmd.main?)
366
+
360
367
  when :fox!
361
368
  require_db
362
369
  Prick::SubCommand.fox(database, username, args)
@@ -50,12 +50,13 @@ module Prick
50
50
  # The hierarchy of environments is defined in the PRICK_ENVIRONMENT_FILE
51
51
  #
52
52
  def expand_filename(dir, filename)
53
- environment = Prick.state.environment
53
+ envs = Prick.state.environments
54
+ env = envs[Prick.state.environment]
54
55
  bash_vars = Prick.state.bash_environment
55
56
 
56
57
  last = nil
57
- for env in [environment] + Environment[environment].ancestors.reverse
58
- bash_vars["ENVIRONMENT"] = env
58
+ for env in [env] + env.ancestors.reverse
59
+ bash_vars["ENVIRONMENT"] = env.name
59
60
  file = expand_variables(filename, bash_vars)
60
61
  last ||= (file != last and file) or return nil # return if no ENVIRONMENT substitution
61
62
  path = (file.start_with?("/") ? file : File.join(dir, file))
@@ -1,150 +1,369 @@
1
+ require 'set'
1
2
  require 'yaml'
2
3
  require 'dsort'
3
4
 
4
- # TODO:
5
- # o Check :parent vs. :inherit (or explain the difference). Also check
6
- # handling of :comment
7
-
5
+ # TODO
6
+ # o Add a CODE type
7
+ # o super
8
8
  module Prick
9
- # An environment as defined in the prick.environment file. The environment is
10
- # constant across all instances of State so we ensure that it is loaded only
11
- # once
12
9
  class Environment
13
- # Environments name
10
+ # The enclosing Environments object. Used in #types
11
+ attr_reader :environments
12
+
13
+ # Environment name (String)
14
14
  attr_reader :name
15
15
 
16
- # Environment comment. If not nil, this environment is considered a user-environment
17
- attr_accessor :comment
16
+ # List of names of inherited environments
17
+ def inherit = assignments[:inherit]
18
+
19
+ # List of directly inherited environment objects. Assigned by the
20
+ # analyzer
21
+ attr_accessor :parents
22
+
23
+ # Ancestors (array of Environment objects) sorted in dependency order
24
+ attr_accessor :ancestors
25
+
26
+ # The ancestor that defines the build attribute or nil if ambigous.
27
+ # Assigned by the analyzer
28
+ attr_accessor :super_environment
18
29
 
19
- # Names of parent environments
20
- def parents = @values[:parents]
30
+ def has_super?() = !super_environment.nil?
21
31
 
22
- # Ancestors sorted in dependency order
23
- def ancestors
24
- @ancestors ||= effective_values[:parents].sort_by { |name| @@SORTED_INDEXES[name] }
32
+ # Map from variable identifier to the environment that defines the value.
33
+ # This assumes there are no ambigous assignments
34
+ def assigners
35
+ @assigners ||= begin
36
+ effective_variables.keys.map { |ident|
37
+ if assignments.key?(ident)
38
+ [ident, self]
39
+ else
40
+ [ident, ancestors.reverse.find { |env| env.assignments.key?(ident) }]
41
+ end
42
+ }.to_h
43
+ end
25
44
  end
26
45
 
27
- # List of variables including :parents
28
- def variables = @@VARIABLES
46
+ # Environment comment
47
+ def comment = assignments[:comment]
29
48
 
30
- # List of user defined variables
31
- def user_variables = @@VARIABLES - [:parents, :comment]
49
+ # Map from variable identifier (Symbol) to type. Type is one of the strings
50
+ # listed in Environments::TYPES
51
+ def types() = @environments.types
32
52
 
33
- # Hash from variable to value
34
- attr_accessor :values
53
+ # Map from variable identifier (Symbol) to value. Inherited variables are not
54
+ # included in #assignments
55
+ attr_reader :assignments
35
56
 
36
- # Hash from variable name to effective value. The effective value is the
37
- # sum of this environment's value and its parents' values
38
- attr_accessor :effective_values
57
+ # Return true if the environment assigns an attribute
58
+ def assign?(ident) = @assignments.key?(ident)
39
59
 
40
- def self.[](name) @@ENVIRONMENTS[name] end
41
- def self.environment?(name) @@ENVIRONMENTS.key?(name) end
42
- def self.environments() @@SORTED_ENVIRONMENTS.map { |key| @@ENVIRONMENTS[key] } end
43
- def self.variables() @@VARIABLES end
60
+ # Map from variable identifier (Symbol) to effective value. Initially equal
61
+ # to #assignments but are later augmented or merged with the corresponding
62
+ # assignments from inherited environments
63
+ attr_accessor :effective_variables
44
64
 
45
- def initialize(name)
65
+ def initialize(environments, name, assignments)
66
+ constrain environments, Environments
67
+ constrain name, String
68
+ constrain assignments, { Symbol => Object }
69
+ @environments = environments
46
70
  @name = name
47
- @comment = nil
48
- @values = variables.map { |key| [key, []] }.to_h
49
- @effective_values = variables.map { |key| [key, []] }.to_h
50
- @@ENVIRONMENTS[name] = self
71
+ @assignments = assignments
72
+ @assignments[:inherit] ||= []
73
+ @effective_variables = assignments.transform_values { |v| v.dup } # Deep-dup
51
74
  end
52
75
 
53
- def self.load_environments(yaml) # hash maps from environment name to object
54
- if hash
55
- parse(yaml)
56
- analyze
57
- end
58
- end
76
+ forward_to :effective_variables, :key?, :[], :[]=, :to_h, :empty?, :size, :each, :map
59
77
 
60
- # does not include the name of the environment
61
- def bash_env
62
- user_variables.map { |variable|
63
- ["PRICK_ENVIRONMENT_#{variable.upcase}", effective_values[variable]]
78
+ def bash() = "build_#{name}" # FIXME build has been out-commented
79
+ def super_bash() = (has_super? ? "build_#{name}_super" : nil) # FIXME same
80
+ def bash_environment
81
+ effective_variables.map { |ident, value|
82
+ ["PRICK_ENVIRONMENT_#{ident.upcase}", value]
64
83
  }.to_h
65
84
  end
66
85
 
86
+ def inspect = "#<#{self.class} @name=#{@name}>"
87
+
67
88
  def dump
68
- puts name
89
+ puts "#{name}:"
69
90
  indent {
70
- values.each { |var, val|
71
- next if val.empty?
72
- puts "#{var}:"
73
- indent { puts val }
74
- }
75
- effective_values.each { |var, val|
76
- next if val.empty?
77
- puts "effective_#{var}:"
78
- indent { puts val }
91
+ self.each { |ident,val|
92
+ # if ident == :build
93
+ # puts "super: #{super_environment&.name || 'false'}"
94
+ # end
95
+ assigner = assigners[ident]&.name || name
96
+ assigner_str = (assigner != name ? " (#{assigner})" : "")
97
+ if types[ident] == "TEXT"
98
+ puts "#{ident}#{assigner_str}:"
99
+ indent { puts val }
100
+ else
101
+ puts "#{ident}#{assigner_str}: #{val.inspect}"
102
+ end
103
+ if ident == :inherit
104
+ puts "ancestors: #{ancestors.map(&:name).inspect}"
105
+ end
79
106
  }
80
107
  }
81
108
  end
109
+ end
110
+
111
+ class Environments
112
+ # Map from environment name to Environment object
113
+ attr_reader :environments
114
+
115
+ # Environments acts like a hash from name to Environment object
116
+ forward_to :@environments, :[], :[]=, :key?, :keys, :values, :each, :map
117
+
118
+ # Maps from variable name (Symbol) to type (String)
119
+ attr_reader :types
120
+
121
+ # List of all variables (Symbol). Same as 'types.keys'
122
+ def variables = types.keys
82
123
 
83
- def self.dump
84
- environments.each(&:dump)
124
+ # List of variable identifiers defined by prick
125
+ def prick_variables = @@PRICK_VARIABLES
126
+
127
+ # List of user defined variables
128
+ def user_variables = variables - prick_variables
129
+
130
+ def initialize(hash)
131
+ @types = @@TYPES.dup
132
+ @environments = {}
133
+ parse_variables(hash)
134
+ parse_environments(hash)
135
+ analyze
85
136
  end
86
137
 
87
- private
88
- @@VARIABLES = []
89
- @@ENVIRONMENTS = {}
90
- @@SORTED_ENVIRONMENTS = []
91
-
92
- def self.yaml_to_array(value)
93
- case value
94
- when /\n/; [value.split("\n")]
95
- when nil ; []
96
- when String; value.split
97
- else [value.to_s]
98
- end
99
- # !value.nil? && Array(value).flatten.compact.map(&:to_s).map(&:split).flatten
138
+ def undent(s)
139
+ s = s[1..] if s[0] == "\n"
140
+ s =~ /^(\s*)\S$/m
141
+ indent = $1
142
+ s.gsub(/^#{indent}/m, "")
100
143
  end
101
144
 
102
- def self.parse(hash)
103
- hash = hash.dup
104
- @@VARIABLES = [:parents, :comment] + yaml_to_array(hash.delete("variables") || "").map(&:to_sym)
105
- for environment, settings in hash
106
- env = Environment.new(environment)
107
- settings ||= {}
108
- if settings.is_a? Hash
109
- for variable, value in settings
110
- if variable == "comment"
111
- env.comment = value
112
- else
113
- variable = (variable == "inherit" ? :parents : variable.to_sym)
114
- value = yaml_to_array(value)
115
- variables.include?(variable) or raise ArgumentError, "Illegal variable: '#{variable}'"
116
- env.values[variable] = value
145
+ def bash_environment
146
+
147
+ end
148
+
149
+ # FIXME build has been out-commented
150
+ def bash_command(environment = nil)
151
+ raise "Not available right now"
152
+ constrain environment, String, nil
153
+ if environment
154
+ env = self[environment] or raise "Unknown environment: '#{environment}'"
155
+ envs = [env]
156
+ else
157
+ envs = environments
158
+ end
159
+
160
+ puts "### SCRIPT by #{File.basename(__FILE__)}"
161
+
162
+ puts undent %(
163
+
164
+ ## STACK METHODS - ChatGPT
165
+
166
+ # global variable
167
+ super_stack=()
168
+
169
+ function push_super_stack() {
170
+ local method=$1
171
+ super_stack+=($method)
172
+ }
173
+
174
+ function pop_super_stack() {
175
+ super_stack=("${super_stack[@]::${#super_stack[@]}-1}")
176
+ }
177
+
178
+ function super() {
179
+ eval ${super_stack[-1]}
180
+ }
181
+ )
182
+
183
+ puts "## ENVIRONMENT METHODS"
184
+ puts
185
+ for name, env in environments
186
+ puts "function build_#{name}() {"
187
+ assigner = env.assigners[:build]
188
+ indent {
189
+ if assigner == env
190
+ if env.has_super?
191
+ puts "push_super_stack #{env.super_bash}"
192
+ end
193
+ puts env[:build]
194
+ if env.has_super?
195
+ puts "pop_super_stack"
117
196
  end
197
+ else
198
+ puts "build_#{assigner.name} # default super"
118
199
  end
119
- else
120
- raise ArgumentError, "Illegal value for '#{environment}': #{settings.inspect}"
200
+ }
201
+ puts "}"
202
+ puts
203
+ if env.has_super? && assigner == env
204
+ puts "build_#{name}_super() { build_#{env.super_environment.name}; }"
205
+ puts
121
206
  end
122
207
  end
208
+
209
+ if environment
210
+ puts "## DEFAULT BUILD METHOD"
211
+ puts
212
+ puts "function build() { build_#{environment}; }"
213
+ puts
214
+ end
215
+ end
216
+
217
+ def dump
218
+ puts "Types"
219
+ indent { types.each { |k,v| puts "#{k}: #{v}" } }
220
+ puts
221
+ puts "Environments"
222
+ indent { environments.values.each { |env| env.dump } }
223
+ end
224
+
225
+ private
226
+ TYPES = %w(BOOLEAN STRING LIST TEXT)
227
+ @@TYPES = { comment: "STRING", inherit: "LIST", build: "TEXT" }
228
+ @@PRICK_VARIABLES = @@TYPES.keys
229
+
230
+ def parse_variables(yaml)
231
+ decls = yaml.delete("variables") || ""
232
+ decls.split.each { |decl|
233
+ decl =~ /^(.*):(.*)$/ or ShellOpts.error "Illegal declaration of '#{decl}' in variable list"
234
+ name, type = $1.to_sym, $2
235
+ type = type.upcase
236
+ TYPES.include?(type) or ShellOpts.error "Illegal type '#{type}'"
237
+ @types[name] = type
238
+ }
123
239
  end
124
240
 
125
- def self.analyze
126
- # Sort environments in dependency order
127
- deps = @@ENVIRONMENTS.map { |name, env| [name, env.parents] }
128
- @@SORTED_ENVIRONMENTS = DSort.dsort(deps)
129
- @@SORTED_INDEXES = @@SORTED_ENVIRONMENTS.map.with_index { |env, idx| [env, idx] }.to_h
241
+ def parse_environments(yaml)
242
+ yaml.each { |environment, variables|
243
+ assignments = variables.map { |ident, value|
244
+ ident = ident.to_sym
245
+ case types[ident]
246
+ when "BOOLEAN"
247
+ [TrueClass, FalseClass].include?(value.class) or raise "Illegal value for #{ident}: #{value}"
248
+ ;
249
+ when "STRING"
250
+ ; # nop
251
+ when "LIST"
252
+ value = value&.split || []
253
+ when "TEXT"
254
+ value = value.chomp
255
+ when nil
256
+ ShellOpts.error "Unknown variable '#{ident}'"
257
+ else
258
+ raise ArgumentError
259
+ end
260
+ [ident, value]
261
+ }.to_h
262
+ @environments[environment] = Environment.new(self, environment, assignments)
263
+ }
264
+ end
130
265
 
131
- # Check for undeclared inherited environments
132
- @@SORTED_ENVIRONMENTS.each { |environment|
133
- @@ENVIRONMENTS.key?(environment) or raise ArgumentError, "Can't find '#{inherited}' environment"
266
+ def analyze
267
+ # Assign Environment#parent
268
+ values.each { |env|
269
+ env.parents = env.inherit.map { |name|
270
+ self[name] or raise ArgumentError, "Can't find '#{name}' environment referred from '#{name}'"
271
+ }
134
272
  }
135
273
 
274
+ # Sort environment names in dependency order
275
+ deps = self.map { |name, env| [name, env.inherit] }
276
+ sorted_environments = DSort.dsort(deps).map { |name| self[name] }
277
+
136
278
  # Compute effective attribute values by processing environments in
137
- # dependency order so that all parents' environments are computed before
279
+ # dependency order so that all inherited environments are computed before
138
280
  # the current environment
139
- for name in @@SORTED_ENVIRONMENTS
140
- env = Environment[name]
141
- env.effective_values = env.values.transform_values { |v| v.dup } # Deep-dup
142
- for parent in env.parents.dup
143
- for var in variables
144
- env.effective_values[var].unshift *Environment[parent].effective_values[var]
281
+ for env in sorted_environments
282
+ for inherited in env.parents
283
+ for ident, type in types
284
+ next if ident == :comment # Comments are not inherited
285
+ next if !inherited.key?(ident)
286
+ value = inherited[ident]
287
+ case type
288
+ when "BOOLEAN"; env[ident] = value if !env.key?(ident)
289
+ when "STRING"; env[ident] ||= value
290
+ when "LIST";
291
+ next if ident == :inherit # Does not accumulate
292
+ # FIXME !inherited.key? should prevent env[ident].nil?
293
+ env[ident] = (value + (env[ident] || [])).uniq
294
+ when "TEXT"; env[ident] = env[ident] || value
295
+ else
296
+ raise ArgumentError
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ # Assign #ancestors
303
+ sorted_indexes = sorted_environments.map.with_index { |env, idx| [env.name, idx] }.to_h
304
+ for env in sorted_environments
305
+ env.ancestors =
306
+ (env.parents + env.parents.map(&:ancestors))
307
+ .flatten
308
+ .uniq
309
+ .sort_by { |env| sorted_indexes[env.name] }
310
+ end
311
+
312
+ # Check ambigous string, text, and boolean definitions (:initial is never
313
+ # ambigous and :comment is special). TODO: Find a faster algorithm
314
+ for env in sorted_environments
315
+ for ident, value in env.effective_variables
316
+ type = types[ident]
317
+
318
+ # Ignore mergeable types (LIST)
319
+ next if !%w(STRING TEXT BOOLEAN).include?(type)
320
+
321
+ # Ignore :initial and :comment
322
+ next if [:initial, :comment].include?(ident)
323
+
324
+ # Ignore if variable is assigned in the current environment and not the
325
+ # special :build attribute
326
+ # next if env.assignments.key?(ident) && ident != :build
327
+ next if env.assignments.key?(ident)
328
+
329
+ # Pool of ancestors. When an environment assigns a value, all ancestors
330
+ # of the environment are removed from the pool. If any remaining environment
331
+ # also assigns the variable, the definition is ambigous. 'pool' and
332
+ # 'queue' works together to provide a set of environments with
333
+ # queue-like properties
334
+ pool = Set.new(env.ancestors)
335
+
336
+ # Queue of ancestors. It is used to iterate the pool environments in
337
+ # reversed dependency order
338
+ queue = env.ancestors.reverse
339
+
340
+ # Look for the first environment that assigns the variable
341
+ while ancestor = queue.shift
342
+ next if !pool.include?(ancestor)
343
+ if ancestor.assignments.key?(ident)
344
+ # Remove all ancestors that are inherited by the assigning environment
345
+ pool -= ancestor.ancestors
346
+ # if ident == :build
347
+ # env.super_environment = ancestor #if ident == :build
348
+ # end
349
+ break
350
+ end
351
+ end
352
+
353
+ # Check that remaining environments do not also assign the variable.
354
+ # The :block attribute is checked even if defined in the current
355
+ # environment to be able to tell if 'super' is ambigous
356
+ while ancestor = queue.shift
357
+ next if !pool.include?(ancestor)
358
+ if ancestor.assign?(ident)
359
+ # if ident == :build && env.assign?(:build)
360
+ # env.super_environment = nil
361
+ # else
362
+ raise ArgumentError, "Ambigious definition of '#{ident}' in #{ancestor.name} environment"
363
+ # end
364
+ end
145
365
  end
146
366
  end
147
- env.effective_values.transform_values! { |v| v.uniq }
148
367
  end
149
368
  end
150
369
  end
@@ -1,9 +1,5 @@
1
1
  require 'fcntl'
2
2
 
3
- require 'forward_to'
4
-
5
- include ForwardTo
6
-
7
3
  module Command
8
4
  class Error < RuntimeError
9
5
  attr_reader :cmd
@@ -5,52 +5,95 @@ module Fmt
5
5
  widths = headers.map(&:size)
6
6
  signs = []
7
7
  types = []
8
+ indexes = [] # absolute position of column. Zero based
8
9
  for i in (0...headers.size)
9
- widths[i] =
10
- (
11
- [widths[i]] +
12
- table.map { |row|
13
- case value = row[i]
14
- when TrueClass, FalseClass
15
- types[i] = 's'
16
- signs[i] = '-'
17
- 5
18
- when NilClass
19
- types[i] = 's'
20
- signs[i] = '-'
21
- 4
22
- when String
23
- types[i] = 's'
24
- signs[i] = '-'
25
- value.size
26
- when Integer
27
- types[i] = 'i'
28
- signs[i] = ''
29
- Math.log10(value) + 1
30
- else
31
- raise ArgumentError, value
32
- end
33
- }
34
- ).max
10
+ value_width =
11
+ table.map { |row|
12
+ case value = row[i]
13
+ when TrueClass, FalseClass
14
+ types[i] = 's'
15
+ signs[i] = '-'
16
+ 5
17
+ when NilClass
18
+ types[i] = 's'
19
+ signs[i] = '-'
20
+ 4
21
+ when String
22
+ types[i] = 's'
23
+ signs[i] = '-'
24
+ value.split("\n").map(&:size).max || 0
25
+ when Integer
26
+ types[i] = 'i'
27
+ signs[i] = ''
28
+ Math.log10(value) + 1
29
+ when Array
30
+ types[i] = 's'
31
+ signs[i] = '-'
32
+ all_size = value.join(' ').size
33
+ if all_size < 60
34
+ all_size
35
+ else
36
+ value.map(&:size).max
37
+ end
38
+ when PrickVersion
39
+ types[i] = 's'
40
+ signs[i] = '-'
41
+ value.to_s.size
42
+ else
43
+ raise ArgumentError, "Illegal value: '#{value}' (#{value.class})"
44
+ end
45
+ }.max || 0
46
+ widths[i] = [widths[i], value_width].max
47
+ indexes[i] = (i == 0 ? 0 : indexes[i-1] + widths[i-1] + 1)
35
48
  end
36
49
 
37
- widths = widths.map { _1 }
38
-
39
50
  for width, value in widths.zip(headers)
40
51
  printf "%-#{width}s ", value
41
52
  end
42
53
  puts
54
+
43
55
  widths.each.with_index { |width, index|
44
56
  char = (headers[index] =~ /^\s*$/ ? " " : "-")
45
57
  printf "%#{width}s ", char * width
46
58
  }
47
59
  puts
60
+
48
61
  for row in table
49
- for sign, width, type, value in signs.zip(widths, types, row)
50
- value = (value.nil? ? "-" : value)
51
- printf "%#{sign}#{width}#{type} ", value
62
+ # for index, sign, width, type, value in signs.zip(indexes, signs, widths, types, row) # FIXME doesn't work?
63
+ for i in (0...headers.size)
64
+ index, sign, width, type, value = indexes[i], signs[i], widths[i], types[i], row[i]
65
+
66
+ if value && value =~ /\n/m
67
+ value = value.split("\n")
68
+ print value.first
69
+ for line in value[1..]
70
+ puts
71
+ print "#{' ' * index}#{line}"
72
+ end
73
+ elsif value.is_a?(Array)
74
+ values = value.dup
75
+ lines = []
76
+ while !values.empty?
77
+ rest = width
78
+ line = []
79
+ while !values.empty? && values.first.size <= rest
80
+ rest -= values.first.size
81
+ line << values.shift
82
+ end
83
+ lines << line.join(" ")
84
+ end
85
+ print lines.first
86
+ for line in lines[1..] || []
87
+ puts
88
+ print "#{' ' * index}#{line}"
89
+ end
90
+ else
91
+ value = (value.nil? ? "-" : value)
92
+ printf "%#{sign}#{width}#{type} ", "#{value}"
93
+ end
52
94
  end
53
95
  puts
54
96
  end
55
97
  end
56
98
  end
99
+
data/lib/prick/state.rb CHANGED
@@ -52,14 +52,17 @@ module Prick
52
52
  # database is absent
53
53
  attr_accessor :username
54
54
 
55
- # Environment name. If not set in the state file, the enviroment is read
56
- # from the database when the first connection is established by the
57
- # #connection method. Use 'Environment[environment]' to get the
55
+ # Map from environment name to environment object
56
+ attr_reader :environments
57
+
58
+ # Name of current environment. If not set in the state file, the enviroment
59
+ # is read from the database when the first connection is established by the
60
+ # #connection method. Use '#environments[environment]' to get the
58
61
  # corresponding Environment object
59
62
  def environment() @environment end
60
63
  def environment=(env)
61
64
  constrain env, String, nil
62
- env.nil? || Environment.environment?(env) or raise "Illegal environment: '#{env}'"
65
+ env.nil? || environments.key?(env) or raise "Illegal environment: '#{env}'"
63
66
  @environment = env
64
67
  end
65
68
 
@@ -122,7 +125,7 @@ module Prick
122
125
  self.environment =
123
126
  environment ||
124
127
  Prick.state.environment ||
125
- Environment.environment?(database_environment) && database_environment ||
128
+ environments.key?(database_environment) && database_environment ||
126
129
  DEFAULT_ENVIRONMENT
127
130
  end
128
131
  if block_given?
@@ -192,14 +195,19 @@ module Prick
192
195
  "PRICK_ENVIRONMENT" => Prick.state.environment&.to_s, # may be the empty string
193
196
  })
194
197
 
195
- # PRICK_ENVIRONMENT_* variables
196
- hash.merge! (Prick.state.environment.nil? ? {} : Environment[Prick.state.environment]&.bash_env || {})
198
+ # PRICK_ENVIRONMENT_* variables. Only defined if the environment is known
199
+ if !Prick.state.environment.nil? && environments.key?(environment)
200
+ hash.merge! environments[environment].bash_environment
201
+ end
197
202
  end
198
203
  end
199
204
 
200
205
  # @scope can be :global (variables are exported), :local (variables are
201
206
  # declared local), or nil (variables are global but not exported)
207
+ #
208
+ # Only non-text variables are emitted
202
209
  def bash_source(vars = nil, scope: nil)
210
+ exclude = Array(exclude || []).flatten.map { _1.to_s }
203
211
  case scope
204
212
  when :global; prefix="export "
205
213
  when :local; prefix="local "
@@ -208,8 +216,11 @@ module Prick
208
216
  raise ArgumentError, "Illegal value for scope: #{scope.inspect}"
209
217
  end
210
218
 
211
- (vars || bash_environment.keys).map { |var|
219
+ vars ||= bash_environment&.keys || []
220
+ assignments = []
221
+ vars.each { |var|
212
222
  val = bash_environment[var]
223
+ # next if val =~ /['"\n]/m # We don't quote
213
224
  if val.is_a?(Array)
214
225
  if val.first.is_a?(Array)
215
226
  val = val.map { |v| v.join("\n") }.join("\n")
@@ -217,13 +228,14 @@ module Prick
217
228
  val = val.join(" ")
218
229
  end
219
230
  end
220
- "#{prefix}#{var}=\"#{val}\"\n"
221
- }.join
231
+ assignments << "#{prefix}#{var}='#{val}'\n"
232
+ }
233
+ assignments.join
222
234
  end
223
235
 
224
236
  # It is an error if the project file exists.
225
- def save_project
226
- !File.exists?(project_file) or Prick.error "Won't overwrite '#{project_file}'"
237
+ def save_project(overwrite: false)
238
+ overwrite || !File.exists?(project_file) or Prick.error "Won't overwrite '#{project_file}'"
227
239
  hash = { name: name, title: title, version: version.to_s, prick: prick_version.to_s }
228
240
  save_yaml(project_file, hash)
229
241
  end
@@ -279,7 +291,7 @@ module Prick
279
291
  puts "#{method}: #{self.send method}"
280
292
  end
281
293
  puts "environments:"
282
- indent { Environment.dump }
294
+ indent { environments.dump }
283
295
  }
284
296
  end
285
297
 
@@ -288,7 +300,7 @@ module Prick
288
300
 
289
301
  def read_yaml(file)
290
302
  begin
291
- YAML.load(File.read file)
303
+ YAML.load File.read(file).sub(/^__END__\n.*/m, "")
292
304
  rescue Errno::ENOENT
293
305
  Prick.error "Can't read #{file}"
294
306
  end
@@ -354,7 +366,7 @@ module Prick
354
366
 
355
367
  def load_environment_file
356
368
  hash = environment_file ? read_yaml(environment_file) : {}
357
- Environment.load_environments(hash)
369
+ @environments = Environments.new(hash)
358
370
  @environment_loaded = true
359
371
  end
360
372
 
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class IO
4
+ def self.capture(&block) # ChatGPT
5
+ block_given? or raise ArgumentError
6
+ stdout = $stdout
7
+ begin
8
+ $stdout = StringIO.new
9
+ yield
10
+ $stdout.string
11
+ ensure
12
+ $stdout = stdout
13
+ end
14
+ end
15
+ end
16
+
17
+ module Prick::SubCommand
18
+ def self.bash(main: false)
19
+ # IO.capture {
20
+ env = Prick.state.environment # Shorthands
21
+ envs = Prick.state.environments
22
+
23
+ puts "#!/usr/bin/bash"
24
+ puts
25
+ puts "# This file is auto-generated by prick. Please don't touch"
26
+ puts
27
+ puts ". bash.include"
28
+ puts
29
+
30
+ # Emit environment. PRICK_ENVIRONMENT_BUILD is excluded because its value
31
+ # is bash source that is hard to escape and there is no use for this
32
+ # variable anyway
33
+ puts "### ENVIRONMENT by state.rb"
34
+ puts
35
+ puts Prick.state.bash_source(scope: :global)
36
+ puts
37
+
38
+ # Emit script
39
+ if env
40
+ envs.bash_command env
41
+ else
42
+ envs.bash_command
43
+ end
44
+
45
+ if main
46
+ puts "### MAIN by prick-load.rb"
47
+ puts
48
+ puts "build"
49
+ end
50
+ # }
51
+ end
52
+ end
53
+
@@ -1,6 +1,6 @@
1
1
  module Prick::SubCommand
2
2
  def self.list_environments(format: :long)
3
- environments = Environment.environments.select { |env| env.comment }
3
+ environments = Prick.state.environments.values.select { |env| env.comment }
4
4
  if format == :short
5
5
  puts environments.map(&:name)
6
6
  else
@@ -16,7 +16,20 @@ module Prick::SubCommand
16
16
  else
17
17
  headers = %w(variable value)
18
18
  vars = Prick.state.bash_environment(all: all).reject { |k,v| k == "PATH" }
19
- rows = vars.map { |k,v| [k, Array(v).flatten.join(" ")] }
19
+ rows = vars.map
20
+ # rows = vars.map { |k,v|
21
+ # if v.is_a?(Array)
22
+ # if v.first&.is_a?(Array)
23
+ # v = v.first
24
+ # else
25
+ # v = v.join(" ")
26
+ # end
27
+ # else
28
+ # v = v.to_s
29
+ # end
30
+ #
31
+ # [k, v]
32
+ # }
20
33
  Fmt.puts_table(headers, rows)
21
34
  end
22
35
  end
@@ -8,7 +8,7 @@ module Prick
8
8
  Git.synchronized? or raise "Won't release: Repository is not synchronized with origin"
9
9
 
10
10
  version = Prick.state.version.increment!(kind).to_s
11
- Prick.state.save_project
11
+ Prick.state.save_project(overwrite: true)
12
12
 
13
13
  Git.add(Prick.state.project_file)
14
14
  Git.add(Prick.state.schema_file)
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.31.0"
4
+ VERSION = "0.33.0"
5
5
  end
data/lib/prick.rb CHANGED
@@ -2,9 +2,15 @@ require 'prick/version.rb'
2
2
 
3
3
  require 'pg_conn'
4
4
 
5
- require 'fixture_fox'
6
5
  require 'indented_io'
6
+
7
7
  require 'constrain'
8
+ include Constrain
9
+
10
+ require 'forward_to'
11
+ include ForwardTo
12
+
13
+ require 'fixture_fox'
8
14
 
9
15
  module Prick
10
16
  class Error < RuntimeError; end
@@ -33,17 +39,18 @@ module Prick
33
39
  end
34
40
 
35
41
  require_relative 'prick/subcommand/subcommand.rb'
42
+ require_relative 'prick/subcommand/prick-bash.rb'
36
43
  require_relative 'prick/subcommand/prick-build.rb'
37
44
  require_relative 'prick/subcommand/prick-clean.rb'
38
45
  require_relative 'prick/subcommand/prick-create.rb'
39
46
  require_relative 'prick/subcommand/prick-drop.rb'
40
- require_relative 'prick/subcommand/prick-set.rb'
41
- require_relative 'prick/subcommand/prick-list.rb'
42
47
  require_relative 'prick/subcommand/prick-fox.rb'
43
48
  require_relative 'prick/subcommand/prick-init.rb'
49
+ require_relative 'prick/subcommand/prick-list.rb'
44
50
  require_relative 'prick/subcommand/prick-make.rb'
45
51
  require_relative 'prick/subcommand/prick-migrate.rb'
46
52
  require_relative 'prick/subcommand/prick-release.rb'
53
+ require_relative 'prick/subcommand/prick-set.rb'
47
54
  require_relative 'prick/subcommand/prick-setup.rb'
48
55
  require_relative 'prick/subcommand/prick-teardown.rb'
49
56
 
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.31.0
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-15 00:00:00.000000000 Z
11
+ date: 2024-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic
@@ -219,6 +219,7 @@ files:
219
219
  - lib/prick/share/migrate/migration/diff.before-tables.sql
220
220
  - lib/prick/share/migrate/migration/diff.tables.sql
221
221
  - lib/prick/state.rb
222
+ - lib/prick/subcommand/prick-bash.rb
222
223
  - lib/prick/subcommand/prick-build.rb
223
224
  - lib/prick/subcommand/prick-clean.rb
224
225
  - lib/prick/subcommand/prick-create.rb
@@ -238,7 +239,7 @@ files:
238
239
  homepage: http://www.nowhere.com/
239
240
  licenses: []
240
241
  metadata: {}
241
- post_install_message:
242
+ post_install_message:
242
243
  rdoc_options: []
243
244
  require_paths:
244
245
  - lib
@@ -254,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
255
  version: '0'
255
256
  requirements: []
256
257
  rubygems_version: 3.3.7
257
- signing_key:
258
+ signing_key:
258
259
  specification_version: 4
259
260
  summary: A release control and management system for postgresql
260
261
  test_files: []