squared 0.0.9 → 0.0.11
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 +4 -4
- data/README.md +2 -6
- data/README.ruby.md +145 -47
- data/lib/squared/app.rb +10 -0
- data/lib/squared/common/base.rb +17 -15
- data/lib/squared/common/class.rb +20 -3
- data/lib/squared/common/format.rb +104 -46
- data/lib/squared/common/prompt.rb +38 -0
- data/lib/squared/common/shell.rb +16 -7
- data/lib/squared/common/system.rb +8 -38
- data/lib/squared/common/task.rb +3 -2
- data/lib/squared/common/utils.rb +69 -0
- data/lib/squared/common.rb +2 -0
- data/lib/squared/config.rb +31 -26
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +285 -137
- data/lib/squared/workspace/project/base.rb +459 -178
- data/lib/squared/workspace/project/git.rb +95 -114
- data/lib/squared/workspace/project/node.rb +306 -160
- data/lib/squared/workspace/project/python.rb +45 -19
- data/lib/squared/workspace/project/ruby.rb +156 -127
- data/lib/squared/workspace/project.rb +0 -3
- data/lib/squared/workspace/repo.rb +100 -93
- data/lib/squared/workspace/series.rb +82 -52
- data/lib/squared/workspace.rb +5 -4
- data/lib/squared.rb +1 -11
- metadata +5 -2
@@ -7,14 +7,28 @@ module Squared
|
|
7
7
|
include Format
|
8
8
|
include ::Rake::DSL
|
9
9
|
|
10
|
+
SCRIPT_OBJ = {
|
11
|
+
run: nil,
|
12
|
+
script: nil,
|
13
|
+
dev: nil,
|
14
|
+
prod: nil,
|
15
|
+
global: false,
|
16
|
+
env: false
|
17
|
+
}.freeze
|
18
|
+
private_constant :SCRIPT_OBJ
|
19
|
+
|
10
20
|
class << self
|
11
21
|
def implement(*objs)
|
12
22
|
objs.each do |obj|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
23
|
+
next unless obj < base_project
|
24
|
+
|
25
|
+
impl_project.unshift(obj)
|
26
|
+
obj.tasks&.each { |task| impl_series.add(task, obj) }
|
27
|
+
if (args = obj.batchargs)
|
28
|
+
impl_series.batch(*args)
|
29
|
+
end
|
30
|
+
if (args = obj.aliasargs)
|
31
|
+
impl_series.alias(*args)
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
@@ -23,87 +37,109 @@ module Squared
|
|
23
37
|
if ref && (ret = impl_project.find { |proj| proj.ref == ref.to_sym })
|
24
38
|
ret
|
25
39
|
elsif path
|
26
|
-
impl_project.find { |proj| proj.
|
40
|
+
impl_project.find { |proj| proj.config?(path) }
|
27
41
|
end
|
28
42
|
end
|
29
43
|
|
44
|
+
def baseref
|
45
|
+
base_project.ref
|
46
|
+
end
|
47
|
+
|
30
48
|
def to_s
|
31
49
|
super.to_s.match(/[^:]+$/)[0]
|
32
50
|
end
|
33
51
|
|
34
|
-
attr_reader :impl_project, :
|
52
|
+
attr_reader :impl_project, :attr_banner
|
53
|
+
attr_accessor :impl_series, :base_project
|
35
54
|
end
|
36
55
|
|
37
56
|
@impl_project = []
|
38
|
-
@
|
57
|
+
@attr_banner = SymSet.new(%i[name project path ref group parent version])
|
39
58
|
|
40
|
-
attr_reader :root, :home, :main, :prefix, :series, :
|
41
|
-
attr_accessor :exception, :warning, :pipe, :verbose
|
59
|
+
attr_reader :root, :home, :main, :prefix, :exception, :warning, :pipe, :verbose, :theme, :series, :closed
|
42
60
|
|
43
|
-
def initialize(home = Dir.pwd, *, main: nil, prefix: nil,
|
44
|
-
|
45
|
-
@
|
61
|
+
def initialize(home = Dir.pwd, *, main: nil, prefix: nil, verbose: nil,
|
62
|
+
common: ARG[:COMMON], pipe: ARG[:PIPE], exception: ARG[:FAIL], **)
|
63
|
+
@home = Pathname.new(home).realdirpath
|
46
64
|
@root = @home.parent
|
65
|
+
@main = (main || @home.basename).to_s.freeze
|
47
66
|
@prefix = prefix
|
48
67
|
@series = Application.impl_series.new(self)
|
49
68
|
@project = {}
|
50
69
|
@extensions = []
|
51
|
-
@
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
env: false
|
58
|
-
}
|
59
|
-
@exception = exception.is_a?(String) ? !env(exception, ignore: '0').nil? : exception
|
60
|
-
@pipe = pipe.is_a?(String) ? !env(pipe, ignore: '0').nil? : pipe
|
61
|
-
@verbose = verbose.nil? ? !@pipe : verbose
|
62
|
-
@warning = @verbose
|
63
|
-
if kwargs.fetch(:common, true)
|
70
|
+
@pipe = env_pipe(pipe, (ARG[:OUT] && env(ARG[:OUT])) || 1, root: @home)
|
71
|
+
@exception = env_bool(exception)
|
72
|
+
@verbose = verbose.nil? ? @pipe != 0 : verbose
|
73
|
+
@warning = @verbose != false
|
74
|
+
@closed = false
|
75
|
+
if common
|
64
76
|
@theme = __get__(:theme)[:workspace]
|
65
|
-
|
77
|
+
ARG[:COLOR] = false if @pipe == 0 || @pipe.is_a?(::Pathname)
|
66
78
|
else
|
67
79
|
@theme = {}
|
68
80
|
end
|
69
|
-
@
|
81
|
+
@script = {
|
82
|
+
group: {},
|
83
|
+
ref: {},
|
84
|
+
group!: {},
|
85
|
+
ref!: {}
|
86
|
+
}.freeze
|
87
|
+
@banner = {
|
88
|
+
group: {},
|
89
|
+
ref: {}
|
90
|
+
}.freeze
|
91
|
+
initialize_session
|
70
92
|
end
|
71
93
|
|
72
|
-
def
|
73
|
-
|
74
|
-
return unless
|
94
|
+
def initialize_session
|
95
|
+
@envname = @main.gsub(/[^\w]+/, '_').upcase.freeze
|
96
|
+
return unless @pipe.is_a?(::Pathname)
|
97
|
+
|
98
|
+
bord = '#' * Project.line_width
|
99
|
+
puts bord, format('Session started on %s by %s', Time.now.to_s, @main), bord
|
100
|
+
end
|
101
|
+
|
102
|
+
def build(parallel: [], **kwargs)
|
103
|
+
kwargs[:parallel] = parallel.map(&:to_s)
|
104
|
+
return if @closed || !enabled?
|
75
105
|
|
76
106
|
@project.each_value do |proj|
|
77
107
|
next unless proj.enabled?
|
78
108
|
|
79
109
|
proj.populate(**kwargs)
|
80
|
-
series.
|
110
|
+
series.populate(proj)
|
81
111
|
end
|
82
112
|
|
83
113
|
Application.impl_project.each { |obj| obj.populate(self, **kwargs) }
|
84
114
|
@extensions.each { |ext| __send__(ext, **kwargs) }
|
85
115
|
|
86
|
-
series.
|
116
|
+
series.build(**kwargs)
|
87
117
|
__build__(**kwargs)
|
88
118
|
|
89
119
|
yield self if block_given?
|
120
|
+
@closed = true
|
90
121
|
end
|
91
122
|
|
92
|
-
def with(val, &blk)
|
123
|
+
def with(*val, group: nil, **kwargs, &blk)
|
93
124
|
@group = nil
|
94
125
|
@ref = nil
|
95
|
-
|
126
|
+
@withargs = kwargs.empty? ? nil : kwargs
|
127
|
+
val = [group || kwargs[:ref]].compact.flatten if val.empty?
|
128
|
+
kind = val.first
|
129
|
+
val = kind if val.size == 1
|
130
|
+
case kind
|
131
|
+
when ::String
|
96
132
|
@group = val
|
97
|
-
|
98
|
-
instance_eval(&blk)
|
99
|
-
@group = nil
|
100
|
-
end
|
101
|
-
elsif val.is_a?(::Symbol)
|
133
|
+
when ::Symbol
|
102
134
|
@ref = val
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
135
|
+
else
|
136
|
+
raise_error('group{Symbol} | ref{String}', hint: 'missing') if block_given?
|
137
|
+
end
|
138
|
+
if block_given?
|
139
|
+
instance_eval(&blk)
|
140
|
+
@group = nil
|
141
|
+
@ref = nil
|
142
|
+
@withargs = nil
|
107
143
|
end
|
108
144
|
self
|
109
145
|
end
|
@@ -128,50 +164,105 @@ module Squared
|
|
128
164
|
script_command :test, script, group, ref
|
129
165
|
end
|
130
166
|
|
167
|
+
def log(script, group: @group, ref: @ref)
|
168
|
+
script_command :log, script, group, ref
|
169
|
+
end
|
170
|
+
|
131
171
|
def exclude(base, group: @group, ref: @ref)
|
132
172
|
script_command :exclude, as_a(base, :to_sym).freeze, group, ref
|
133
173
|
end
|
134
174
|
|
135
|
-
def
|
175
|
+
def banner(*args, command: true, styles: nil, border: nil, group: @group, ref: @ref)
|
176
|
+
data = { command: command, order: [], styles: check_style(styles, empty: false), border: check_style(border) }
|
177
|
+
args.each do |meth|
|
178
|
+
if meth.is_a?(::Array)
|
179
|
+
found = false
|
180
|
+
meth = meth.select do |val|
|
181
|
+
case val
|
182
|
+
when ::Symbol
|
183
|
+
found = true
|
184
|
+
Application.attr_banner.include?(val)
|
185
|
+
when ::String
|
186
|
+
true
|
187
|
+
else
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
if !found
|
192
|
+
next
|
193
|
+
elsif meth.size == 1
|
194
|
+
meth = meth.first
|
195
|
+
end
|
196
|
+
elsif !Application.attr_banner.include?(meth = meth.to_sym)
|
197
|
+
next
|
198
|
+
end
|
199
|
+
data[:order] << meth
|
200
|
+
end
|
201
|
+
unless !command && data[:order].empty?
|
202
|
+
if group
|
203
|
+
label = :group
|
204
|
+
items = as_a(group)
|
205
|
+
else
|
206
|
+
label = :ref
|
207
|
+
items = ref ? as_a(ref) : %i[_]
|
208
|
+
end
|
209
|
+
items.each { |val| @banner[label][val.to_sym] = data }
|
210
|
+
end
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
def add(path, val = nil, **kwargs, &blk)
|
215
|
+
if @withargs
|
216
|
+
data = @withargs.dup
|
217
|
+
data.merge!(kwargs)
|
218
|
+
kwargs = data
|
219
|
+
end
|
136
220
|
ref = if kwargs.key?(:ref)
|
221
|
+
kwargs = kwargs.dup unless @withargs
|
137
222
|
kwargs.delete(:ref)
|
138
|
-
elsif
|
223
|
+
elsif @ref.is_a?(::Symbol)
|
139
224
|
@ref
|
140
225
|
end
|
141
|
-
|
142
|
-
|
226
|
+
if @group.is_a?(::String) && !kwargs.key?(:group)
|
227
|
+
kwargs = kwargs.dup unless @withargs
|
228
|
+
kwargs[:group] = @group
|
229
|
+
end
|
230
|
+
path = root_path(path)
|
231
|
+
val = (val || path.basename).to_s
|
232
|
+
name = val.to_sym
|
233
|
+
index = 0
|
234
|
+
while @project[name]
|
235
|
+
index += 1
|
236
|
+
name = :"#{val}-#{index}"
|
237
|
+
end
|
143
238
|
proj = ((if !ref.is_a?(::Class)
|
144
239
|
Application.find(ref, path: path)
|
145
240
|
elsif ref < Project::Base
|
146
241
|
ref
|
147
|
-
end) || Project::Base).new(
|
148
|
-
@project[name
|
242
|
+
end) || Project::Base).new(self, path, task_name(name), **kwargs)
|
243
|
+
@project[name] = proj
|
149
244
|
__get__(:project)[name] = proj unless kwargs[:private]
|
245
|
+
proj.instance_eval(&blk) if block_given?
|
150
246
|
self
|
151
247
|
end
|
152
248
|
|
153
|
-
def group(
|
154
|
-
root_path(path).children.map do |
|
155
|
-
next unless
|
249
|
+
def group(path, val, override: {}, **kwargs, &blk)
|
250
|
+
root_path(path).children.map do |dir|
|
251
|
+
next unless dir.directory?
|
156
252
|
|
157
|
-
index = 0
|
158
253
|
basename = dir.basename.to_s.to_sym
|
159
|
-
|
160
|
-
index += 1
|
161
|
-
basename = :"#{basename}-#{index}"
|
162
|
-
end
|
163
|
-
[basename, dir, override[basename]]
|
254
|
+
[dir, basename, override[basename]]
|
164
255
|
end
|
165
|
-
.each do |
|
256
|
+
.each do |dir, basename, opts|
|
166
257
|
args = kwargs.dup
|
167
258
|
args.merge!(opts) if opts
|
168
|
-
add(
|
259
|
+
add(dir, basename, group: val, **args, &blk)
|
169
260
|
end
|
170
261
|
self
|
171
262
|
end
|
172
263
|
|
173
264
|
def compose(name, &blk)
|
174
|
-
namespace(task_name(name), &blk)
|
265
|
+
namespace(task_name(name), &blk) if block_given?
|
175
266
|
self
|
176
267
|
end
|
177
268
|
|
@@ -191,49 +282,77 @@ module Squared
|
|
191
282
|
end
|
192
283
|
end
|
193
284
|
end
|
194
|
-
apply_style(data || theme, name,
|
285
|
+
apply_style(data || theme, name, args, empty: empty) unless target && !data
|
195
286
|
self
|
196
287
|
end
|
197
288
|
|
198
|
-
def
|
199
|
-
|
289
|
+
def find(path = nil, name: nil)
|
290
|
+
path = root_path(path) if path
|
291
|
+
ret = @project.find { |_, item| (path && item.path == path) || (name && item.name == name.to_s) }&.last
|
292
|
+
return ret unless block_given?
|
200
293
|
|
201
|
-
|
202
|
-
group.to_sym
|
203
|
-
else
|
204
|
-
:"_#{ref || @ref || ''}"
|
205
|
-
end
|
206
|
-
data = { command: command, order: [], styles: check_style(styles, empty: false), border: check_style(border) }
|
207
|
-
order.flatten.each do |val|
|
208
|
-
case (val = val.to_sym)
|
209
|
-
when :name, :project, :path, :ref, :group
|
210
|
-
data[:order] << val
|
211
|
-
end
|
212
|
-
end
|
213
|
-
@banner[key] = data if command || !data[:order].empty?
|
294
|
+
yield ret if ret
|
214
295
|
self
|
215
296
|
end
|
216
297
|
|
217
|
-
def
|
218
|
-
|
219
|
-
@script[:group][group.to_sym]
|
220
|
-
elsif ref
|
221
|
-
@script[:ref][ref.to_sym]
|
222
|
-
elsif (val = @script[:build])
|
223
|
-
r = @script[:ref][:_]
|
224
|
-
g = @script[:group][:_]
|
225
|
-
args.empty? || (!r && !g) || (r && args.include?(r)) || (g && args.include?(g)) ? [val, @script[:env]] : []
|
226
|
-
else
|
227
|
-
[]
|
228
|
-
end
|
298
|
+
def find_base(obj)
|
299
|
+
Application.impl_project.find { |proj| obj.instance_of?(proj) }
|
229
300
|
end
|
230
301
|
|
231
|
-
def
|
232
|
-
@
|
233
|
-
|
234
|
-
|
302
|
+
def task_name(val, desc: false)
|
303
|
+
ret = @prefix ? task_join(@prefix, val) : val.to_s
|
304
|
+
desc ? ret.split(':').join(ARG[:SPACE]) : ret
|
305
|
+
end
|
306
|
+
|
307
|
+
def task_namespace(val, first: false)
|
308
|
+
return nil unless (ret = val.to_s.split(':')).size > 1
|
235
309
|
|
236
|
-
|
310
|
+
first ? ret.first : task_join(*ret[0..-2])
|
311
|
+
end
|
312
|
+
|
313
|
+
def task_join(*val)
|
314
|
+
val.join(':')
|
315
|
+
end
|
316
|
+
|
317
|
+
def task_resolve(obj, key)
|
318
|
+
tasks = []
|
319
|
+
if (base = task_base?(key))
|
320
|
+
tasks << key if obj.has?(key, baseref)
|
321
|
+
elsif (batch = series.batch_get(key))
|
322
|
+
obj.allref.each do |ref|
|
323
|
+
next unless (data = batch[ref])
|
324
|
+
|
325
|
+
data.each do |val|
|
326
|
+
if (items = task_resolve(obj, val)).empty?
|
327
|
+
tasks.clear
|
328
|
+
break
|
329
|
+
end
|
330
|
+
tasks += items
|
331
|
+
end
|
332
|
+
return tasks unless tasks.empty?
|
333
|
+
end
|
334
|
+
elsif task_extend?(obj, key)
|
335
|
+
tasks << key
|
336
|
+
end
|
337
|
+
ret = []
|
338
|
+
if tasks.empty?
|
339
|
+
return [] if (base && !obj.ref?(baseref)) || !(data = series.alias_get(key))
|
340
|
+
|
341
|
+
obj.allref.each do |ref|
|
342
|
+
next unless (alt = data[ref])
|
343
|
+
|
344
|
+
ret = task_resolve(obj, alt)
|
345
|
+
break unless ret.empty?
|
346
|
+
end
|
347
|
+
else
|
348
|
+
tasks.each do |val|
|
349
|
+
target = task_join(obj.name, series.name_get(val))
|
350
|
+
return [] unless task_defined?(target)
|
351
|
+
|
352
|
+
ret << target
|
353
|
+
end
|
354
|
+
end
|
355
|
+
ret
|
237
356
|
end
|
238
357
|
|
239
358
|
def root_path(*args)
|
@@ -244,24 +363,33 @@ module Squared
|
|
244
363
|
home.join(*args)
|
245
364
|
end
|
246
365
|
|
247
|
-
def
|
248
|
-
|
366
|
+
def script_find(*args)
|
367
|
+
args.reverse_each do |val|
|
368
|
+
next unless val && (ret = val.is_a?(::Symbol) ? @script[:ref!][val] : @script[:group!][val.to_sym])
|
369
|
+
|
370
|
+
return ret
|
371
|
+
end
|
372
|
+
@script[:ref!][:_] || SCRIPT_OBJ
|
249
373
|
end
|
250
374
|
|
251
|
-
def
|
252
|
-
|
253
|
-
|
375
|
+
def script_get(group: nil, ref: nil)
|
376
|
+
if group
|
377
|
+
@script[:group][group.to_sym]
|
378
|
+
elsif ref
|
379
|
+
@script[:ref][ref]
|
380
|
+
end
|
254
381
|
end
|
255
382
|
|
256
|
-
def
|
257
|
-
|
383
|
+
def banner_get(*ref, group: nil)
|
384
|
+
ret = nil
|
385
|
+
return ret if group && (ret = @banner[:group][group.to_sym])
|
258
386
|
|
259
|
-
ret = val
|
260
|
-
|
387
|
+
ref.reverse_each { |val| return ret if (ret = @banner[:ref][val]) }
|
388
|
+
@banner[:ref][:_]
|
261
389
|
end
|
262
390
|
|
263
391
|
def to_s
|
264
|
-
root.to_s
|
392
|
+
(home? ? home : root).to_s
|
265
393
|
end
|
266
394
|
|
267
395
|
def inspect
|
@@ -269,42 +397,55 @@ module Squared
|
|
269
397
|
end
|
270
398
|
|
271
399
|
def enabled?
|
272
|
-
!@extensions.empty? || @project.any?
|
400
|
+
!@extensions.empty? || @project.values.any?(&:enabled?)
|
401
|
+
end
|
402
|
+
|
403
|
+
def task_base?(key)
|
404
|
+
series.base?(key)
|
273
405
|
end
|
274
406
|
|
275
407
|
def task_extend?(obj, key)
|
276
408
|
series.extend?(obj, key)
|
277
409
|
end
|
278
410
|
|
279
|
-
def
|
280
|
-
|
411
|
+
def task_include?(obj, key, ref = nil)
|
412
|
+
task_base?(key) ? obj.has?(key, ref || baseref) : task_extend?(obj, key)
|
281
413
|
end
|
282
414
|
|
283
|
-
def
|
284
|
-
|
415
|
+
def task_defined?(*key)
|
416
|
+
::Rake::Task.task_defined?(key.size == 1 ? key.first : task_join(*key))
|
285
417
|
end
|
286
418
|
|
287
|
-
def
|
288
|
-
|
419
|
+
def dev?(**kwargs)
|
420
|
+
script?(:dev, **kwargs)
|
421
|
+
end
|
289
422
|
|
290
|
-
|
423
|
+
def prod?(**kwargs)
|
424
|
+
script?(:prod, **kwargs)
|
291
425
|
end
|
292
426
|
|
293
|
-
|
427
|
+
def home?
|
428
|
+
!(proj = find(home)).nil? && proj.enabled?
|
429
|
+
end
|
294
430
|
|
295
|
-
def
|
296
|
-
|
297
|
-
|
298
|
-
end
|
299
|
-
return unless series.some?(:build)
|
431
|
+
def baseref
|
432
|
+
Application.baseref
|
433
|
+
end
|
300
434
|
|
301
|
-
|
435
|
+
def invokeargs
|
436
|
+
{ exception: exception, warning: warning }
|
437
|
+
end
|
438
|
+
|
439
|
+
private
|
302
440
|
|
303
|
-
|
304
|
-
|
441
|
+
def __build__(default: nil, **)
|
442
|
+
if default && task_defined?(t = task_name(default)) && !task_defined?(n = ::Rake.application.default_task_name)
|
443
|
+
task n => t
|
444
|
+
end
|
445
|
+
end
|
305
446
|
|
306
|
-
|
307
|
-
|
447
|
+
def puts(*args)
|
448
|
+
puts_oe(*args, pipe: pipe)
|
308
449
|
end
|
309
450
|
|
310
451
|
def script_command(task, val, group, ref)
|
@@ -319,23 +460,30 @@ module Squared
|
|
319
460
|
self
|
320
461
|
end
|
321
462
|
|
322
|
-
def
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
end
|
329
|
-
if pat.is_a?(::Regexp)
|
330
|
-
script = script[state == :prod ? 1 : 0] if script.is_a?(::Array)
|
331
|
-
pat.match?(script)
|
463
|
+
def script_set(data, group: nil, ref: nil)
|
464
|
+
data.freeze
|
465
|
+
if group
|
466
|
+
as_a(group).each { |val| @script[:group!][val.to_sym] = data }
|
467
|
+
elsif ref
|
468
|
+
as_a(ref).each { |val| @script[:ref!][val.to_sym] = data }
|
332
469
|
else
|
333
|
-
|
470
|
+
@script[:ref!][:_] = data
|
334
471
|
end
|
335
472
|
end
|
336
473
|
|
337
|
-
def
|
338
|
-
|
474
|
+
def script_obj
|
475
|
+
SCRIPT_OBJ.dup
|
476
|
+
end
|
477
|
+
|
478
|
+
def script?(state, target: nil, pat: nil, group: nil, ref: baseref, global: false)
|
479
|
+
data = script_find(ref, group)
|
480
|
+
if global
|
481
|
+
target = data[:script] || data[:run] if target.nil?
|
482
|
+
pat = data[state] if pat.nil?
|
483
|
+
end
|
484
|
+
return false if state == :prod && data[:dev] == true && data[:global]
|
485
|
+
|
486
|
+
target && pat.is_a?(::Regexp) ? as_a(target).any? { |val| pat.match?(val) } : pat == true
|
339
487
|
end
|
340
488
|
end
|
341
489
|
end
|