prick 0.31.0 → 0.32.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: fed99dbfdf6e89642b4e8fdddf976efa58b1c8b75d9169fa460bd8190805cc63
4
+ data.tar.gz: 97fec32bbd130043b2c782f133ab5af30e9bcc5c42d4c83a26311f486786d671
5
5
  SHA512:
6
- metadata.gz: f59c9a5ac1284eeb006327370308630ec24b3dc30dfa9af10f0695f6b0501bcfe207750b78893315f2478364eee576f780a65662d6827aa77fe2b654f5b38a8a
7
- data.tar.gz: 7958f8d49c73c5c1b2de604fb671367c2704ef3d373c0847ae9861378f719b2e6e9d02661c8b3c36ce91541e8adb71875f3d233f7151277c64d51439087203ad
6
+ metadata.gz: 35859b1f387d1bf8ccb04a37a853d509e8038102f9521a7b4b35884db5d2682e5a06bd3dda9f4aff704a105603d9df17a43d2d1bbfa66f39ba751584b73b7cb9
7
+ data.tar.gz: cc7dfb26f5c10b1489d82f5be5c824e7d7d118de29d9ceae6b424e26506701650da57ed800107ce8ad10ea0bda3e1d29934000170702b285ae3d7a3a8123426f
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,366 @@
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}"
79
+ def super_bash() = (has_super? ? "build_#{name}_super" : nil)
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
82
120
 
83
- def self.dump
84
- environments.each(&:dump)
121
+ # List of all variables (Symbol). Same as 'types.keys'
122
+ def variables = types.keys
123
+
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
+ def bash_command(environment = nil)
150
+ constrain environment, String, nil
151
+ if environment
152
+ env = self[environment] or raise "Unknown environment: '#{environment}'"
153
+ envs = [env]
154
+ else
155
+ envs = environments
156
+ end
157
+
158
+ puts "### SCRIPT by #{File.basename(__FILE__)}"
159
+
160
+ puts undent %(
161
+
162
+ ## STACK METHODS - ChatGPT
163
+
164
+ # global variable
165
+ super_stack=()
166
+
167
+ function push_super_stack() {
168
+ local method=$1
169
+ super_stack+=($method)
170
+ }
171
+
172
+ function pop_super_stack() {
173
+ super_stack=("${super_stack[@]::${#super_stack[@]}-1}")
174
+ }
175
+
176
+ function super() {
177
+ eval ${super_stack[-1]}
178
+ }
179
+ )
180
+
181
+ puts "## ENVIRONMENT METHODS"
182
+ puts
183
+ for name, env in environments
184
+ puts "function build_#{name}() {"
185
+ assigner = env.assigners[:build]
186
+ indent {
187
+ if assigner == env
188
+ if env.has_super?
189
+ puts "push_super_stack #{env.super_bash}"
190
+ end
191
+ puts env[:build]
192
+ if env.has_super?
193
+ puts "pop_super_stack"
117
194
  end
195
+ else
196
+ puts "build_#{assigner.name} # default super"
118
197
  end
119
- else
120
- raise ArgumentError, "Illegal value for '#{environment}': #{settings.inspect}"
198
+ }
199
+ puts "}"
200
+ puts
201
+ if env.has_super? && assigner == env
202
+ puts "build_#{name}_super() { build_#{env.super_environment.name}; }"
203
+ puts
121
204
  end
122
205
  end
206
+
207
+ if environment
208
+ puts "## DEFAULT BUILD METHOD"
209
+ puts
210
+ puts "function build() { build_#{environment}; }"
211
+ puts
212
+ end
123
213
  end
124
214
 
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
215
+ def dump
216
+ puts "Types"
217
+ indent { types.each { |k,v| puts "#{k}: #{v}" } }
218
+ puts
219
+ puts "Environments"
220
+ indent { environments.values.each { |env| env.dump } }
221
+ end
222
+
223
+ private
224
+ TYPES = %w(BOOLEAN STRING LIST TEXT)
225
+ @@TYPES = { comment: "STRING", inherit: "LIST", build: "TEXT" }
226
+ @@PRICK_VARIABLES = @@TYPES.keys
130
227
 
131
- # Check for undeclared inherited environments
132
- @@SORTED_ENVIRONMENTS.each { |environment|
133
- @@ENVIRONMENTS.key?(environment) or raise ArgumentError, "Can't find '#{inherited}' environment"
228
+ def parse_variables(yaml)
229
+ decls = yaml.delete("variables") || ""
230
+ decls.split.each { |decl|
231
+ decl =~ /^(.*):(.*)$/ or ShellOpts.error "Illegal declaration of '#{decl}' in variable list"
232
+ name, type = $1.to_sym, $2
233
+ type = type.upcase
234
+ TYPES.include?(type) or ShellOpts.error "Illegal type '#{type}'"
235
+ @types[name] = type
134
236
  }
237
+ end
238
+
239
+ def parse_environments(yaml)
240
+ yaml.each { |environment, variables|
241
+ assignments = variables.map { |ident, value|
242
+ ident = ident.to_sym
243
+ case types[ident]
244
+ when "BOOLEAN"
245
+ [TrueClass, FalseClass].include?(value.class) or raise "Illegal value for #{ident}: #{value}"
246
+ ;
247
+ when "STRING"
248
+ ; # nop
249
+ when "LIST"
250
+ value = value.split
251
+ when "TEXT"
252
+ value = value.chomp
253
+ when nil
254
+ ShellOpts.error "Unknown variable '#{ident}'"
255
+ else
256
+ raise ArgumentError
257
+ end
258
+ [ident, value]
259
+ }.to_h
260
+ @environments[environment] = Environment.new(self, environment, assignments)
261
+ }
262
+ end
263
+
264
+ def analyze
265
+ # Assign Environment#parent
266
+ values.each { |env|
267
+ env.parents = env.inherit.map { |name|
268
+ self[name] or raise ArgumentError, "Can't find '#{name}' environment referred from '#{name}'"
269
+ }
270
+ }
271
+
272
+ # Sort environment names in dependency order
273
+ deps = self.map { |name, env| [name, env.inherit] }
274
+ sorted_environments = DSort.dsort(deps).map { |name| self[name] }
135
275
 
136
276
  # Compute effective attribute values by processing environments in
137
- # dependency order so that all parents' environments are computed before
277
+ # dependency order so that all inherited environments are computed before
138
278
  # 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]
279
+ for env in sorted_environments
280
+ for inherited in env.parents
281
+ for ident, type in types
282
+ next if ident == :comment # Comments are not inherited
283
+ next if !inherited.key?(ident)
284
+ value = inherited[ident]
285
+ case type
286
+ when "BOOLEAN"; env[ident] = value if !env.key?(ident)
287
+ when "STRING"; env[ident] ||= value
288
+ when "LIST";
289
+ next if ident == :inherit # Does not accumulate
290
+ # FIXME !inherited.key? should prevent env[ident].nil?
291
+ env[ident] = (value + (env[ident] || [])).uniq
292
+ when "TEXT"; env[ident] = env[ident] || value
293
+ else
294
+ raise ArgumentError
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ # Assign #ancestors
301
+ sorted_indexes = sorted_environments.map.with_index { |env, idx| [env.name, idx] }.to_h
302
+ for env in sorted_environments
303
+ env.ancestors =
304
+ (env.parents + env.parents.map(&:ancestors))
305
+ .flatten
306
+ .uniq
307
+ .sort_by { |env| sorted_indexes[env.name] }
308
+ end
309
+
310
+ # Check ambigous string, text, and boolean definitions (:initial is never
311
+ # ambigous and :comment is special). TODO: Find a faster algorithm
312
+ for env in sorted_environments
313
+ for ident, value in env.effective_variables
314
+ type = types[ident]
315
+
316
+ # Ignore mergeable types (LIST)
317
+ next if !%w(STRING TEXT BOOLEAN).include?(type)
318
+
319
+ # Ignore :initial and :comment
320
+ next if [:initial, :comment].include?(ident)
321
+
322
+ # Ignore if variable is assigned in the current environment and not the
323
+ # special :build attribute
324
+ next if env.assignments.key?(ident) && ident != :build
325
+
326
+ # Pool of ancestors. When an environment assigns a value, all ancestors
327
+ # of the environment are removed from the pool. If any remaining environment
328
+ # also assigns the variable, the definition is ambigous. 'pool' and
329
+ # 'queue' works together to provide a set of environments with
330
+ # queue-like properties
331
+ pool = Set.new(env.ancestors)
332
+
333
+ # Queue of ancestors. It is used to iterate the pool environments in
334
+ # reversed dependency order
335
+ queue = env.ancestors.reverse
336
+
337
+ # Look for the first environment that assigns the variable
338
+ while ancestor = queue.shift
339
+ next if !pool.include?(ancestor)
340
+ if ancestor.assignments.key?(ident)
341
+ # Remove all ancestors that are inherited by the assigning environment
342
+ pool -= ancestor.ancestors
343
+ if ident == :build
344
+ env.super_environment = ancestor #if ident == :build
345
+ end
346
+ break
347
+ end
348
+ end
349
+
350
+ # Check that remaining environments do not also assign the variable.
351
+ # The :block attribute is checked even if defined in the current
352
+ # environment to be able to tell if 'super' is ambigous
353
+ while ancestor = queue.shift
354
+ next if !pool.include?(ancestor)
355
+ if ancestor.assign?(ident)
356
+ if ident == :build && env.assign?(:build)
357
+ env.super_environment = nil
358
+ else
359
+ raise ArgumentError, "Ambigious definition of '#{ident}' in #{ancestor.name} environment"
360
+ end
361
+ end
145
362
  end
146
363
  end
147
- env.effective_values.transform_values! { |v| v.uniq }
148
364
  end
149
365
  end
150
366
  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?
@@ -193,13 +196,18 @@ module Prick
193
196
  })
194
197
 
195
198
  # PRICK_ENVIRONMENT_* variables
196
- hash.merge! (Prick.state.environment.nil? ? {} : Environment[Prick.state.environment]&.bash_env || {})
199
+ if !Prick.state.environment.nil?
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.32.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.32.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-21 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: []