prick 0.31.0 → 0.33.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: 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: []