squared 0.5.6 → 0.5.8
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/CHANGELOG.md +65 -1
- data/README.md +16 -12
- data/lib/squared/common/format.rb +3 -7
- data/lib/squared/common/shell.rb +3 -3
- data/lib/squared/common/system.rb +2 -3
- data/lib/squared/common/utils.rb +5 -2
- data/lib/squared/config.rb +3 -3
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +3 -1
- data/lib/squared/workspace/project/base.rb +88 -96
- data/lib/squared/workspace/project/docker.rb +16 -17
- data/lib/squared/workspace/project/git.rb +30 -33
- data/lib/squared/workspace/project/node.rb +38 -40
- data/lib/squared/workspace/project/python.rb +25 -33
- data/lib/squared/workspace/project/ruby.rb +106 -107
- data/lib/squared/workspace/project/support/class.rb +189 -57
- data/lib/squared/workspace/repo.rb +0 -3
- data/lib/squared/workspace/series.rb +16 -18
- metadata +1 -1
@@ -10,8 +10,9 @@ module Squared
|
|
10
10
|
include Common::Shell
|
11
11
|
extend Forwardable
|
12
12
|
|
13
|
-
OPT_VALUE = /\A([^=]+)
|
14
|
-
|
13
|
+
OPT_VALUE = /\A-{0,2}([^= ]+)(?: *= *| +)(.+)\z/
|
14
|
+
OPT_SINGLE = /\A-([A-Za-z\d])(.+)\z/
|
15
|
+
private_constant :OPT_VALUE, :OPT_SINGLE
|
15
16
|
|
16
17
|
class << self
|
17
18
|
include Common::Format
|
@@ -45,11 +46,23 @@ module Squared
|
|
45
46
|
exit 1 unless pass || confirm("Run? [#{sub_style(target, styles: styles)}]", 'N')
|
46
47
|
end
|
47
48
|
|
49
|
+
def delete_key(target, *args, value: false, reverse: false, count: -1)
|
50
|
+
ret = []
|
51
|
+
args.each do |val|
|
52
|
+
next if (opts = target.grep(matchopt(val, value))).empty?
|
53
|
+
|
54
|
+
opts = opts.first(count) if count >= 0
|
55
|
+
opts.send(reverse ? :reverse_each : :each) { |key| target.delete(key) }
|
56
|
+
ret.concat(opts)
|
57
|
+
end
|
58
|
+
ret
|
59
|
+
end
|
60
|
+
|
48
61
|
def strip(val)
|
49
62
|
return [] unless val
|
50
63
|
|
51
64
|
val = shell_split(val) if val.is_a?(String)
|
52
|
-
val.map { |s| s.sub(
|
65
|
+
val.map { |s| s.sub(OPT_SINGLE, '\1=\2').sub(OPT_VALUE, '\1=\2') }.reject(&:empty?)
|
53
66
|
end
|
54
67
|
|
55
68
|
def select(list, bare: true, no: true, single: false, double: false)
|
@@ -63,10 +76,7 @@ module Squared
|
|
63
76
|
|
64
77
|
def arg?(target, *args, value: false, **)
|
65
78
|
r, s = args.partition { |val| val.is_a?(Regexp) }
|
66
|
-
unless s.empty?
|
67
|
-
s.map! { |val| Regexp.escape(val.start_with?('-') ? val : shell_option(val)) }
|
68
|
-
r << /\A(?:#{s.join('|')})#{value ? '[ =].' : '(?: |=|\z)'}/
|
69
|
-
end
|
79
|
+
r << matchopts(s, value) unless s.empty?
|
70
80
|
s = target.to_a.compact
|
71
81
|
r.any? { |pat| s.any?(pat) }
|
72
82
|
end
|
@@ -74,6 +84,27 @@ module Squared
|
|
74
84
|
def pattern?(val)
|
75
85
|
val.match?(/(?:\A\^|\$\z)/) || val.match?(/(?:\.[*+]|\(\?:|\\[dsw]|\[.+\]|\{\d+,?\d*\})/)
|
76
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def matchopt(val, value = false)
|
91
|
+
/\A#{val.size == 1 ? shortopt(val) : longopt(val, value)}/
|
92
|
+
end
|
93
|
+
|
94
|
+
def matchopts(list, value = false)
|
95
|
+
a, b = list.partition { |val| val.size == 1 || val.match?(OPT_SINGLE) }
|
96
|
+
/\A(?:#{shortopt(*a)}|#{longopt(*b, value)})/
|
97
|
+
end
|
98
|
+
|
99
|
+
def shortopt(*group)
|
100
|
+
group.map! { |s| s.delete_prefix('-') }
|
101
|
+
"-(?:#{Regexp.escape(group.join('|'))})(?:\\z|[^ =]| +[^ -])"
|
102
|
+
end
|
103
|
+
|
104
|
+
def longopt(*group, value)
|
105
|
+
group.map! { |s| s.delete_prefix('--') }
|
106
|
+
"--(?:#{Regexp.escape(group.join('|'))})(?:#{value ? '=[^ ]| +[^ -]' : '[= ]|\z'})"
|
107
|
+
end
|
77
108
|
end
|
78
109
|
|
79
110
|
attr_reader :target, :extras, :found, :errors, :values, :project, :path
|
@@ -81,10 +112,14 @@ module Squared
|
|
81
112
|
def_delegators :@target, :+, :-, :<<, :any?, :none?, :include?, :add, :add?, :find, :find_all, :find_index,
|
82
113
|
:merge, :delete, :delete?, :delete_if, :grep, :grep_v, :inspect, :to_a, :to_s
|
83
114
|
def_delegators :@extras, :empty?, :each, :each_with_index, :partition, :dup, :first, :last, :shift, :unshift,
|
84
|
-
:pop, :push, :concat, :index, :
|
115
|
+
:pop, :push, :concat, :index, :join, :map, :map!, :detect, :select, :select!, :reject, :size,
|
116
|
+
:delete_at
|
85
117
|
|
86
118
|
def_delegator :@extras, :delete, :remove
|
119
|
+
def_delegator :@extras, :delete_at, :remove_at
|
87
120
|
def_delegator :@extras, :delete_if, :remove_if
|
121
|
+
def_delegator :@extras, :find_all, :detect_all
|
122
|
+
def_delegator :@extras, :find_index, :detect_index
|
88
123
|
|
89
124
|
def initialize(opts, list, target = Set.new, project: nil, path: nil, **kwargs, &blk)
|
90
125
|
@target = target.is_a?(Set) ? target : target.to_set
|
@@ -95,7 +130,7 @@ module Squared
|
|
95
130
|
parse(list, opts, **kwargs, &blk)
|
96
131
|
end
|
97
132
|
|
98
|
-
def parse(list, opts = extras, no: nil, single: nil, args: false, first: nil, &blk)
|
133
|
+
def parse(list, opts = extras, no: nil, single: nil, args: false, first: nil, underscore: nil, &blk)
|
99
134
|
@extras = []
|
100
135
|
@values = []
|
101
136
|
bare = []
|
@@ -108,6 +143,7 @@ module Squared
|
|
108
143
|
i = []
|
109
144
|
f = []
|
110
145
|
si = []
|
146
|
+
bl = []
|
111
147
|
list.flat_map do |val|
|
112
148
|
x, y = val.split('|', 2)
|
113
149
|
if y
|
@@ -142,10 +178,12 @@ module Squared
|
|
142
178
|
si << flag
|
143
179
|
when 'v'
|
144
180
|
@values << Regexp.escape(flag)
|
181
|
+
when '!'
|
182
|
+
bl << flag
|
145
183
|
else
|
146
184
|
next
|
147
185
|
end
|
148
|
-
m << flag if val[n + 2] == 'm'
|
186
|
+
m << flag if flag.size == 1 && val[n + 2] == 'm'
|
149
187
|
bare << flag if val.end_with?('?')
|
150
188
|
else
|
151
189
|
bare << val
|
@@ -153,6 +191,22 @@ module Squared
|
|
153
191
|
end
|
154
192
|
no = (no || []).map { |val| (n = val.index('=')) ? val[0, n] : val }
|
155
193
|
bare.concat(no)
|
194
|
+
if underscore
|
195
|
+
tr = ->(list) { list.map { |val| val.tr('-', '_') } }
|
196
|
+
@values.concat(tr.call(@values))
|
197
|
+
bare.concat(tr.call(bare))
|
198
|
+
e.concat(tr.call(e))
|
199
|
+
b.concat(tr.call(b))
|
200
|
+
m.concat(tr.call(m))
|
201
|
+
p.concat(tr.call(p))
|
202
|
+
q.concat(tr.call(q))
|
203
|
+
qq.concat(tr.call(qq))
|
204
|
+
i.concat(tr.call(i))
|
205
|
+
f.concat(tr.call(f))
|
206
|
+
si.concat(tr.call(si))
|
207
|
+
bl.concat(tr.call(bl))
|
208
|
+
no.concat(tr.call(no))
|
209
|
+
end
|
156
210
|
numtype = [
|
157
211
|
[i, /\A\d+\z/],
|
158
212
|
[f, /\A\d*(?:\.\d+)?\z/],
|
@@ -168,7 +222,7 @@ module Squared
|
|
168
222
|
add "-#{opt}"
|
169
223
|
elsif bare.include?(opt)
|
170
224
|
add(opt.size == 1 ? "-#{opt}" : "--#{opt}")
|
171
|
-
elsif opt.start_with?(
|
225
|
+
elsif opt.start_with?(/no[-_]/) && no.include?(name = opt[3..-1])
|
172
226
|
add "--no-#{name}"
|
173
227
|
else
|
174
228
|
if opt =~ OPT_VALUE
|
@@ -179,9 +233,15 @@ module Squared
|
|
179
233
|
add shell_option(key, val, merge: merge)
|
180
234
|
elsif q.include?(key)
|
181
235
|
add quote_option(key, val, double: qq.include?(key), merge: merge)
|
182
|
-
elsif p.include?(key)
|
183
|
-
|
184
|
-
|
236
|
+
elsif p.include?(key)
|
237
|
+
if val.match?(/\A(["']).+\1\z/)
|
238
|
+
add shell_option(key, val, escape: false, merge: merge, sep: sep)
|
239
|
+
elsif path
|
240
|
+
add quote_option(key, path + val, merge: merge, sep: sep)
|
241
|
+
else
|
242
|
+
push opt
|
243
|
+
end
|
244
|
+
elsif b.include?(key) || (bl.include?(key) && %w[true false].include?(val)) || numcheck.call(key, val)
|
185
245
|
add basic_option(key, val, merge: merge)
|
186
246
|
elsif merge
|
187
247
|
add basic_option(key, val, merge: true)
|
@@ -216,16 +276,34 @@ module Squared
|
|
216
276
|
self
|
217
277
|
end
|
218
278
|
|
279
|
+
def append_any(*args, quote: true, **kwargs)
|
280
|
+
(args.empty? ? extras : args.flatten).each do |val|
|
281
|
+
if exist?(val)
|
282
|
+
add_path(val, **kwargs)
|
283
|
+
elsif quote
|
284
|
+
add_quote(val, **kwargs)
|
285
|
+
else
|
286
|
+
add val
|
287
|
+
end
|
288
|
+
end
|
289
|
+
self
|
290
|
+
end
|
291
|
+
|
292
|
+
def delete_key(*args, **kwargs)
|
293
|
+
OptionPartition.delete_key(target, *args, **kwargs)
|
294
|
+
self
|
295
|
+
end
|
296
|
+
|
219
297
|
def values_of(*args, strict: true, first: false, last: false)
|
220
298
|
eq, s = strict ? ['=', '[^ ]+'] : ['(?:=| +)', '[^-][^ ]*']
|
221
|
-
|
222
|
-
|
223
|
-
|
299
|
+
g = ["\"((?:[^\"]|(?<=\\\\)\"(?!$#{windows? ? '| ' : ''}))*)\""]
|
300
|
+
g << "'((?:[^']|'\\\\'')*)'" unless windows?
|
301
|
+
g << "(#{s})"
|
224
302
|
args.map! do |opt|
|
225
303
|
if opt.size == 1
|
226
304
|
/(?:\A| )-#{opt} ?([^ ]+)/
|
227
305
|
else
|
228
|
-
/(?:\A| )--#{opt + eq}(?:#{
|
306
|
+
/(?:\A| )--#{opt + eq}(?:#{g.join('|')})/
|
229
307
|
end
|
230
308
|
end
|
231
309
|
ret = []
|
@@ -239,64 +317,79 @@ module Squared
|
|
239
317
|
end
|
240
318
|
return ret unless first || last
|
241
319
|
|
242
|
-
if last
|
243
|
-
|
320
|
+
if last.is_a?(Numeric)
|
321
|
+
ret.last(last)
|
322
|
+
elsif last
|
323
|
+
ret.last
|
244
324
|
else
|
245
325
|
first.is_a?(Numeric) ? ret.first(first) : ret.first
|
246
326
|
end
|
247
327
|
end
|
248
328
|
|
249
329
|
def uniq(list)
|
250
|
-
|
251
|
-
list.reject
|
252
|
-
next true if items.include?(s = nameonly(val))
|
253
|
-
|
254
|
-
pat = /\A#{s = fill_option(s)}(?:#{s.start_with?('--') ? '[= ]' : '.*'}|\z)/
|
255
|
-
any? { |opt| opt.match?(pat) }
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
def uniq!(list)
|
260
|
-
n = size
|
261
|
-
concat uniq(list)
|
262
|
-
extras if size > n
|
330
|
+
ignore = map { |val| nameonly(val) }
|
331
|
+
list.reject { |val| ignore.include?(s = nameonly(val)) || any?(OptionPartition.send(:matchopt, s)) }
|
263
332
|
end
|
264
333
|
|
265
334
|
def clear(opts = nil, errors: false, **kwargs)
|
266
335
|
styles = project.theme[:inline] if project
|
267
|
-
if
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
@errors.clear
|
336
|
+
if errors
|
337
|
+
OptionPartition.clear(target, self.errors, styles: styles, **kwargs)
|
338
|
+
self.errors.clear
|
339
|
+
return self unless opts
|
272
340
|
end
|
273
|
-
|
341
|
+
opts ||= extras
|
342
|
+
OptionPartition.clear(target, if found.empty?
|
343
|
+
opts
|
344
|
+
else
|
345
|
+
opts.reject { |val| found.include?(val) }
|
346
|
+
end, styles: styles, **kwargs)
|
274
347
|
opts.clear
|
275
348
|
self
|
276
349
|
end
|
277
350
|
|
278
|
-
def adjoin(*args, start: false)
|
351
|
+
def adjoin(*args, with: nil, start: false)
|
279
352
|
i = -1
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
353
|
+
temp = to_a
|
354
|
+
if with
|
355
|
+
pat = case with
|
356
|
+
when String, Symbol
|
357
|
+
/\A#{Regexp.escape(with)}\z/
|
358
|
+
when Array
|
359
|
+
OptionPartition.send(:matchopts, with)
|
360
|
+
else
|
361
|
+
with
|
362
|
+
end
|
363
|
+
temp.each_with_index do |val, index|
|
364
|
+
if val.to_s.match?(pat)
|
288
365
|
i = index + (start.is_a?(Numeric) ? start : 1)
|
289
366
|
break
|
290
367
|
end
|
291
|
-
|
368
|
+
end
|
369
|
+
else
|
370
|
+
temp.each_with_index do |val, index|
|
371
|
+
if i == 0
|
372
|
+
next unless val.start_with?('-')
|
373
|
+
|
374
|
+
i = index
|
375
|
+
break
|
376
|
+
elsif index > 0 && !val.start_with?('-')
|
377
|
+
if start
|
378
|
+
i = index + (start.is_a?(Numeric) ? start : 1)
|
379
|
+
break
|
380
|
+
end
|
381
|
+
i = 0
|
382
|
+
end
|
292
383
|
end
|
293
384
|
end
|
294
385
|
if i > 0
|
295
386
|
if args.empty?
|
296
387
|
args = dup
|
297
388
|
reset
|
389
|
+
else
|
390
|
+
args.each { |val| remove val }
|
298
391
|
end
|
299
|
-
args =
|
392
|
+
args = temp[0...i] + args + temp[i..-1]
|
300
393
|
target.clear
|
301
394
|
end
|
302
395
|
merge args
|
@@ -304,12 +397,12 @@ module Squared
|
|
304
397
|
end
|
305
398
|
|
306
399
|
def add_path(*args, **kwargs)
|
307
|
-
add shell_quote(path ? path.join(*args) : File.join(*args), **kwargs)
|
400
|
+
add shell_quote(path ? path.join(*args) : File.join(*args), option: false, **kwargs)
|
308
401
|
self
|
309
402
|
end
|
310
403
|
|
311
404
|
def add_quote(*args, **kwargs)
|
312
|
-
merge(args.map { |val| shell_quote(val, **kwargs) })
|
405
|
+
merge(args.map! { |val| shell_quote(val, **kwargs) })
|
313
406
|
self
|
314
407
|
end
|
315
408
|
|
@@ -332,7 +425,8 @@ module Squared
|
|
332
425
|
if path
|
333
426
|
found.each { |val| add_path(val) }
|
334
427
|
else
|
335
|
-
|
428
|
+
found.map! { |val| shell_quote(val) } if quote
|
429
|
+
merge found
|
336
430
|
end
|
337
431
|
end
|
338
432
|
self
|
@@ -340,6 +434,7 @@ module Squared
|
|
340
434
|
|
341
435
|
def reset(errors: false)
|
342
436
|
extras.clear
|
437
|
+
found.clear
|
343
438
|
clear(errors: true) if errors
|
344
439
|
self
|
345
440
|
end
|
@@ -366,11 +461,48 @@ module Squared
|
|
366
461
|
OptionPartition.arg?(target, *args, **kwargs)
|
367
462
|
end
|
368
463
|
|
464
|
+
def exist?(*args, add: false, first: false, last: false)
|
465
|
+
return false unless path
|
466
|
+
return path.join(*args).exist? unless args.empty?
|
467
|
+
|
468
|
+
if first || last
|
469
|
+
return false unless (val = first ? self.first : self.last).is_a?(String)
|
470
|
+
|
471
|
+
path.join(val).exist?.tap do |ret|
|
472
|
+
next unless add && ret
|
473
|
+
|
474
|
+
add_first(path: true, reverse: !first)
|
475
|
+
end
|
476
|
+
else
|
477
|
+
each_with_index do |val, index|
|
478
|
+
next unless val.is_a?(String) && path.join(val).exist?
|
479
|
+
|
480
|
+
if add
|
481
|
+
remove_at index
|
482
|
+
add_path val
|
483
|
+
end
|
484
|
+
return true
|
485
|
+
end
|
486
|
+
false
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def uniq!(list)
|
491
|
+
unless (list = uniq(list)).empty?
|
492
|
+
concat list
|
493
|
+
self
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
369
497
|
private
|
370
498
|
|
371
499
|
def nameonly(val)
|
372
500
|
val[OPT_VALUE, 1] || val
|
373
501
|
end
|
502
|
+
|
503
|
+
def windows?
|
504
|
+
Rake::Win32.windows?
|
505
|
+
end
|
374
506
|
end
|
375
507
|
|
376
508
|
class JoinSet < Set
|
@@ -412,7 +544,9 @@ module Squared
|
|
412
544
|
end
|
413
545
|
ret[i.last] = val
|
414
546
|
end
|
415
|
-
|
547
|
+
return ret unless block_given?
|
548
|
+
|
549
|
+
ret.reject(&blk)
|
416
550
|
end
|
417
551
|
|
418
552
|
def and(*args)
|
@@ -437,9 +571,7 @@ module Squared
|
|
437
571
|
end
|
438
572
|
|
439
573
|
def done
|
440
|
-
|
441
|
-
clear
|
442
|
-
ret
|
574
|
+
to_s.tap { clear }
|
443
575
|
end
|
444
576
|
|
445
577
|
def to_s
|
@@ -119,7 +119,6 @@ module Squared
|
|
119
119
|
target = branch || manifest
|
120
120
|
stage = nil
|
121
121
|
opts = %w[force rebase detach submodules fail no-update gc]
|
122
|
-
newline = ARGV.any?(/^repo:/)
|
123
122
|
desc = lambda do |val, alt = nil|
|
124
123
|
if (ver = branch || alt)
|
125
124
|
val = val.sub('{0}', "opts*=#{opts.join(',')}")
|
@@ -170,7 +169,6 @@ module Squared
|
|
170
169
|
val
|
171
170
|
end
|
172
171
|
stage = 'init'
|
173
|
-
puts if newline
|
174
172
|
opts = repo_opts "-u #{u}", "-m #{m}.xml"
|
175
173
|
opts << "-g #{g}" if g
|
176
174
|
opts << '--submodules' if repo_submodules?(args.include?('submodules'))
|
@@ -206,7 +204,6 @@ module Squared
|
|
206
204
|
end
|
207
205
|
opts << "-j#{ENV.fetch('REPO_JOBS', Rake::CpuCounter.count)}" unless opts.any?(/^--?j(?:obs)?/)
|
208
206
|
opts << '--fetch-submodules' if repo_submodules?
|
209
|
-
puts unless !newline || stage == 'init'
|
210
207
|
begin
|
211
208
|
repo_run("#{repo_bin} sync #{opts.uniq.join(' ')}", exception: opts.include?('--fail-fast'))
|
212
209
|
rescue Errno::ENOENT => e
|
@@ -49,11 +49,7 @@ module Squared
|
|
49
49
|
|
50
50
|
def base_set(obj)
|
51
51
|
TASK_BASE.clear
|
52
|
-
|
53
|
-
obj.tasks
|
54
|
-
else
|
55
|
-
obj.tasks.reject { |val| TASK_KEYS.include?(val) }
|
56
|
-
end).freeze)
|
52
|
+
TASK_BASE.concat(obj.tasks.reject { |val| TASK_KEYS.include?(val) })
|
57
53
|
end
|
58
54
|
|
59
55
|
private
|
@@ -210,27 +206,25 @@ module Squared
|
|
210
206
|
|
211
207
|
def chain?(val)
|
212
208
|
@chain.each_value do |tasks|
|
213
|
-
tasks.flatten(1).each do |
|
214
|
-
next unless
|
209
|
+
tasks.flatten(1).each do |name|
|
210
|
+
next unless (task = invoked_get(name))
|
215
211
|
|
216
|
-
if
|
217
|
-
return true
|
218
|
-
end
|
212
|
+
return true if name == val || task.prerequisites.any? { |pr| pr == val && Rake::Task[pr].already_invoked }
|
219
213
|
end
|
220
214
|
end
|
221
215
|
false
|
222
216
|
end
|
223
217
|
|
224
218
|
def multiple?(val = nil)
|
225
|
-
already_invoked?
|
219
|
+
already_invoked? multiple, val
|
226
220
|
end
|
227
221
|
|
228
222
|
def sync?(val = nil)
|
229
|
-
already_invoked?
|
223
|
+
already_invoked? sync, val
|
230
224
|
end
|
231
225
|
|
232
226
|
def parallel?(val = nil)
|
233
|
-
already_invoked?
|
227
|
+
already_invoked? parallel, val
|
234
228
|
end
|
235
229
|
|
236
230
|
def exclude?(key, empty = false)
|
@@ -239,12 +233,16 @@ module Squared
|
|
239
233
|
|
240
234
|
private
|
241
235
|
|
236
|
+
def invoked_get(name)
|
237
|
+
return unless Rake::Task.task_defined?(name) && (ret = Rake::Task[name]).already_invoked
|
238
|
+
|
239
|
+
ret
|
240
|
+
end
|
241
|
+
|
242
242
|
def already_invoked?(list, val)
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
Rake::Task.tasks.any? { |obj| obj.already_invoked && list.include?(obj.name) }
|
247
|
-
end
|
243
|
+
return Rake::Task.tasks.any? { |obj| obj.already_invoked && list.include?(obj.name) } unless val
|
244
|
+
|
245
|
+
list.include?(val) && !invoked_get(val).nil?
|
248
246
|
end
|
249
247
|
end
|
250
248
|
|