squared 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.ruby.md +3 -2
- data/lib/squared/common/base.rb +91 -0
- data/lib/squared/common/format.rb +14 -5
- data/lib/squared/common/shell.rb +7 -7
- data/lib/squared/common/system.rb +4 -1
- data/lib/squared/common/task.rb +5 -1
- data/lib/squared/common.rb +1 -78
- data/lib/squared/config.rb +7 -7
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +306 -0
- data/lib/squared/{repo → workspace}/project/base.rb +47 -41
- data/lib/squared/{repo → workspace}/project/git.rb +71 -53
- data/lib/squared/{repo → workspace}/project/node.rb +17 -18
- data/lib/squared/{repo → workspace}/project/python.rb +3 -3
- data/lib/squared/{repo → workspace}/project/ruby.rb +7 -8
- data/lib/squared/{repo → workspace}/project.rb +1 -1
- data/lib/squared/workspace/repo.rb +201 -0
- data/lib/squared/workspace/series.rb +125 -0
- data/lib/squared/{repo.rb → workspace.rb} +6 -3
- data/lib/squared.rb +9 -8
- metadata +13 -10
- data/lib/squared/repo/workspace.rb +0 -552
@@ -1,552 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Squared
|
4
|
-
module Repo
|
5
|
-
class Workspace
|
6
|
-
include Common
|
7
|
-
include Format
|
8
|
-
include System
|
9
|
-
include Task
|
10
|
-
include ::Rake::DSL
|
11
|
-
|
12
|
-
TASK_NAME = {
|
13
|
-
build: [],
|
14
|
-
refresh: [],
|
15
|
-
depend: [],
|
16
|
-
outdated: [],
|
17
|
-
doc: [],
|
18
|
-
test: [],
|
19
|
-
copy: [],
|
20
|
-
clean: []
|
21
|
-
}
|
22
|
-
TASK_BASE = {}
|
23
|
-
TASK_KEYS = TASK_NAME.keys.freeze
|
24
|
-
private_constant :TASK_NAME, :TASK_BASE
|
25
|
-
|
26
|
-
class << self
|
27
|
-
def implement(*objs)
|
28
|
-
objs.each do |obj|
|
29
|
-
next unless obj < Project::Base
|
30
|
-
|
31
|
-
project_kind.unshift(obj)
|
32
|
-
obj.tasks&.each do |val|
|
33
|
-
TASK_NAME[val] = []
|
34
|
-
TASK_BASE[val] = obj
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def find(path: nil, ref: nil)
|
40
|
-
if ref.is_a?(::Symbol) || ref.is_a?(::String)
|
41
|
-
ret = project_kind.find { |proj| proj.to_sym == ref.to_sym }
|
42
|
-
return ret if ret
|
43
|
-
end
|
44
|
-
project_kind.find { |proj| proj.is_a?(path) } if path
|
45
|
-
end
|
46
|
-
|
47
|
-
def to_s
|
48
|
-
super.to_s.match(/[^:]+$/)[0]
|
49
|
-
end
|
50
|
-
|
51
|
-
attr_reader :project_kind
|
52
|
-
end
|
53
|
-
|
54
|
-
@project_kind = []
|
55
|
-
|
56
|
-
attr_reader :main, :root, :home, :series, :theme, :sync, :multiple, :parallel
|
57
|
-
attr_accessor :manifest, :manifest_url, :exception, :pipe, :verbose, :warning
|
58
|
-
|
59
|
-
def initialize(main, *, common: true, **)
|
60
|
-
@main = main.to_s
|
61
|
-
@home = if (val = env('REPO_HOME'))
|
62
|
-
home = Pathname.new(val)
|
63
|
-
if @main == home.basename.to_s
|
64
|
-
@root = home.parent
|
65
|
-
if home.exist?
|
66
|
-
@root = nil unless home.directory?
|
67
|
-
elsif !@root.exist?
|
68
|
-
@root.mkpath
|
69
|
-
elsif !empty?(@root)
|
70
|
-
if repo_prompt(@root)
|
71
|
-
@override = true
|
72
|
-
else
|
73
|
-
@root = nil
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
raise ArgumentError, message('REPO_HOME', val, hint: 'invalid') unless @root
|
78
|
-
|
79
|
-
home.realdirpath
|
80
|
-
elsif (val = env('REPO_ROOT'))
|
81
|
-
@root = Pathname.new(val).realdirpath
|
82
|
-
if !@root.exist?
|
83
|
-
@root.mkpath
|
84
|
-
elsif !empty?(@root)
|
85
|
-
raise ArgumentError, message('REPO_ROOT', val, hint: 'exist') unless repo_prompt(@root)
|
86
|
-
|
87
|
-
@override = true
|
88
|
-
end
|
89
|
-
@root.join(@main).realdirpath
|
90
|
-
else
|
91
|
-
empty?(pwd = Pathname.pwd) ? pwd.join(@main) : pwd
|
92
|
-
end
|
93
|
-
@root ||= @home.parent
|
94
|
-
@series = TASK_NAME.dup
|
95
|
-
@project = {}
|
96
|
-
@script = {
|
97
|
-
group: {},
|
98
|
-
ref: {},
|
99
|
-
build: nil,
|
100
|
-
dev: nil,
|
101
|
-
prod: nil
|
102
|
-
}
|
103
|
-
@exception = !env('PIPE_FAIL', ignore: '0').nil?
|
104
|
-
@pipe = !env('PIPE_OUT', ignore: '0').nil?
|
105
|
-
@verbose = !@pipe
|
106
|
-
@warning = !empty?(@root, repo: false)
|
107
|
-
@sync = []
|
108
|
-
@multiple = []
|
109
|
-
@parallel = []
|
110
|
-
@theme = common && @verbose ? __get__(:theme)[:workspace] : {}
|
111
|
-
end
|
112
|
-
|
113
|
-
def build(**kwargs)
|
114
|
-
return unless enabled?
|
115
|
-
|
116
|
-
default = kwargs[:default]
|
117
|
-
multi = env('REPO_SYNC', ignore: '0') ? [] : kwargs.fetch(:parallel, []).map!(&:to_sym)
|
118
|
-
|
119
|
-
group = {}
|
120
|
-
parent = {}
|
121
|
-
incl = []
|
122
|
-
@project.each do |name, proj|
|
123
|
-
next unless proj.enabled?
|
124
|
-
|
125
|
-
series.each do |key, items|
|
126
|
-
target = "#{name}:#{key}"
|
127
|
-
case key
|
128
|
-
when :build, :refresh, :depend, :copy, :outdated, :doc, :test, :clean
|
129
|
-
valid = proj.has?(key) || ::Rake::Task.task_defined?(target)
|
130
|
-
next unless valid || (key == :refresh && series[:build].include?(target = "#{name}:build"))
|
131
|
-
else
|
132
|
-
next unless task_defined?(proj, key)
|
133
|
-
end
|
134
|
-
if proj.group
|
135
|
-
incl << proj.group
|
136
|
-
(group[:"#{key}:#{proj.group}"] ||= []).push(target)
|
137
|
-
else
|
138
|
-
items << target
|
139
|
-
end
|
140
|
-
next unless (base = find_base(proj))
|
141
|
-
|
142
|
-
unless (id = base.to_sym.to_s) == proj.group
|
143
|
-
incl << id
|
144
|
-
(parent[:"#{key}:#{id}"] ||= []).push(target)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
proj.populate(**kwargs)
|
148
|
-
end
|
149
|
-
series.merge!(parent) if incl.uniq.size > 1
|
150
|
-
series.merge!(group)
|
151
|
-
series[:refresh].clear if series[:refresh].all? { |target| target.end_with?(':build') }
|
152
|
-
|
153
|
-
if repo?
|
154
|
-
branch = env('REPO_MANIFEST') || read_manifest
|
155
|
-
target = branch || manifest
|
156
|
-
stage = nil
|
157
|
-
failfast = true
|
158
|
-
cmd = []
|
159
|
-
newline = ARGV.index { |val| val.start_with?('repo:') }.to_i > 0
|
160
|
-
parse_opts = lambda do |args|
|
161
|
-
collect_args(args, :opts).each do |val|
|
162
|
-
case val
|
163
|
-
when 'no-fail'
|
164
|
-
failfast = false
|
165
|
-
when 'force'
|
166
|
-
cmd << '--force-checkout'
|
167
|
-
when 'rebase'
|
168
|
-
cmd << '--rebase'
|
169
|
-
when 'detach'
|
170
|
-
cmd << '--detach'
|
171
|
-
when 'gc'
|
172
|
-
cmd << '--auto-gc'
|
173
|
-
when 'no-update'
|
174
|
-
cmd << '--no-manifest-update'
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
status = lambda do |val, alt = nil|
|
179
|
-
ver = branch || alt
|
180
|
-
ver ? message('repo', val.sub('{0}', 'opts*=force,rebase,detach,gc,no-update,no-fail'), ver) : 'inactive'
|
181
|
-
end
|
182
|
-
|
183
|
-
namespace :repo do |repo|
|
184
|
-
desc status.('all[{0}]')
|
185
|
-
task :all, [:opts] do |_, args|
|
186
|
-
parse_opts.(args)
|
187
|
-
stage ||= :all
|
188
|
-
repo[:sync].invoke
|
189
|
-
@project.select do |_, proj|
|
190
|
-
if proj.enabled?
|
191
|
-
proj.depend
|
192
|
-
proj.build?
|
193
|
-
end
|
194
|
-
end
|
195
|
-
.each_value { |proj| proj.has?(:dev) ? proj.refresh : proj.build }
|
196
|
-
end
|
197
|
-
|
198
|
-
desc status.("init[manifest?=#{target},{0}]", target)
|
199
|
-
task :init, [:manifest, :opts] do |_, args|
|
200
|
-
parse_opts.(args)
|
201
|
-
stage = :init
|
202
|
-
puts if newline
|
203
|
-
system("repo init -u #{manifest_url} -m #{args.manifest || target}.xml", chdir: root)
|
204
|
-
repo[:all].invoke
|
205
|
-
end
|
206
|
-
|
207
|
-
desc status.('sync[{0}]')
|
208
|
-
task :sync, [:opts] do |_, args|
|
209
|
-
raise LoadError, message('repo is not initialized', 'rake repo:init') unless branch || stage == :init
|
210
|
-
|
211
|
-
parse_opts.(args)
|
212
|
-
cmd << "-j#{ENV.fetch('REPO_JOBS', ::Rake::CpuCounter.count)}"
|
213
|
-
cmd << '--fail-fast' if failfast
|
214
|
-
puts if newline && stage != :init
|
215
|
-
begin
|
216
|
-
shell("repo sync #{cmd.join(' ')}", chdir: root, exception: failfast)
|
217
|
-
rescue StandardError => e
|
218
|
-
emphasize(e, title: "rake stash repo:#{stage || 'sync'}")
|
219
|
-
raise
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
Workspace.project_kind.each { |obj| obj.populate(self, **kwargs) }
|
226
|
-
|
227
|
-
if !series[:build].empty?
|
228
|
-
init = [:depend, dev? && !series[:refresh].empty? ? :refresh : :build]
|
229
|
-
|
230
|
-
task default: default || init[1]
|
231
|
-
|
232
|
-
desc 'init'
|
233
|
-
task init: init
|
234
|
-
elsif default && !series[default].empty?
|
235
|
-
task default: default
|
236
|
-
end
|
237
|
-
|
238
|
-
series.each do |key, items|
|
239
|
-
next if items.empty?
|
240
|
-
|
241
|
-
s = key.to_s
|
242
|
-
if items.size > 1
|
243
|
-
multiple << s
|
244
|
-
unless (valid = multi.include?(key))
|
245
|
-
group = key.to_s
|
246
|
-
valid = multi.include?(group.split(':').first.to_sym) if group.include?(':')
|
247
|
-
end
|
248
|
-
if valid
|
249
|
-
desc "#{key} (thread)"
|
250
|
-
multitask key => items
|
251
|
-
parallel << s
|
252
|
-
|
253
|
-
desc "#{key} (sync)"
|
254
|
-
task "#{key}:sync": items
|
255
|
-
sync << "#{key}:sync"
|
256
|
-
multiple << "#{key}:sync"
|
257
|
-
next
|
258
|
-
end
|
259
|
-
end
|
260
|
-
desc s
|
261
|
-
task key => items
|
262
|
-
end
|
263
|
-
|
264
|
-
yield self if block_given?
|
265
|
-
end
|
266
|
-
|
267
|
-
def repo(url, manifest = 'latest', run: nil, dev: nil, prod: nil)
|
268
|
-
@manifest_url = url
|
269
|
-
@manifest = manifest
|
270
|
-
script = case (val = env('REPO_BUILD'))
|
271
|
-
when 'verbose'
|
272
|
-
@verbose = true
|
273
|
-
run ? "#{run}:verbose" : nil
|
274
|
-
when 'silent'
|
275
|
-
@verbose = false
|
276
|
-
@warning = false
|
277
|
-
run
|
278
|
-
else
|
279
|
-
val || run
|
280
|
-
end
|
281
|
-
case env('REPO_WARN')
|
282
|
-
when '0'
|
283
|
-
@warning = false
|
284
|
-
when '1'
|
285
|
-
@warning = true
|
286
|
-
end
|
287
|
-
return self unless script
|
288
|
-
|
289
|
-
run(script, dev: bool_match(env('REPO_DEV'), dev), prod: bool_match(env('REPO_DEV'), prod))
|
290
|
-
end
|
291
|
-
|
292
|
-
def run(script, group: nil, ref: nil, **kwargs)
|
293
|
-
if group || ref
|
294
|
-
script_command :run, script, group, ref
|
295
|
-
else
|
296
|
-
@script[:build] = script
|
297
|
-
@script[:dev] = kwargs[:dev]
|
298
|
-
@script[:prod] = kwargs[:prod]
|
299
|
-
self
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def depend(script, group: nil, ref: nil)
|
304
|
-
script_command :depend, script, group, ref
|
305
|
-
end
|
306
|
-
|
307
|
-
def clean(script, group: nil, ref: nil)
|
308
|
-
script_command :clean, script, group, ref
|
309
|
-
end
|
310
|
-
|
311
|
-
def doc(script, group: nil, ref: nil)
|
312
|
-
script_command :doc, script, group, ref
|
313
|
-
end
|
314
|
-
|
315
|
-
def test(script, group: nil, ref: nil)
|
316
|
-
script_command :test, script, group, ref
|
317
|
-
end
|
318
|
-
|
319
|
-
def add(name, path = nil, ref: nil, **kwargs)
|
320
|
-
path = root_path((path || name).to_s)
|
321
|
-
project = if !ref.is_a?(::Class)
|
322
|
-
Workspace.find(path: path, ref: ref)
|
323
|
-
elsif ref < Project::Base
|
324
|
-
ref
|
325
|
-
end
|
326
|
-
instance = (project || Project::Base).new(name, path, self, **kwargs)
|
327
|
-
@project[n = name.to_sym] = instance
|
328
|
-
__get__(:project)[n] = instance unless kwargs[:private]
|
329
|
-
self
|
330
|
-
end
|
331
|
-
|
332
|
-
def group(name, path, **kwargs)
|
333
|
-
root_path(path).children.map do |ent|
|
334
|
-
next unless (dir = Pathname.new(ent)).directory?
|
335
|
-
|
336
|
-
index = 0
|
337
|
-
basename = dir.basename.to_s.to_sym
|
338
|
-
override = kwargs.delete(basename)
|
339
|
-
while @project[basename]
|
340
|
-
index += 1
|
341
|
-
basename = :"#{basename}-#{index}"
|
342
|
-
end
|
343
|
-
[basename, dir, override]
|
344
|
-
end
|
345
|
-
.each do |basename, dir, override|
|
346
|
-
opts = kwargs.dup
|
347
|
-
opts.merge!(override) if override
|
348
|
-
add(basename, dir, group: name, **opts)
|
349
|
-
end
|
350
|
-
self
|
351
|
-
end
|
352
|
-
|
353
|
-
def compose(name, &blk)
|
354
|
-
namespace(name, &blk)
|
355
|
-
self
|
356
|
-
end
|
357
|
-
|
358
|
-
def apply(&blk)
|
359
|
-
instance_eval(&blk)
|
360
|
-
self
|
361
|
-
end
|
362
|
-
|
363
|
-
def style(name, *args, target: nil, empty: false)
|
364
|
-
data = nil
|
365
|
-
if target
|
366
|
-
as_a(target).each_with_index do |val, i|
|
367
|
-
if i == 0
|
368
|
-
break unless (data = __get__(:theme)[val.to_sym])
|
369
|
-
else
|
370
|
-
data = data[val.to_sym] ||= {}
|
371
|
-
end
|
372
|
-
end
|
373
|
-
return unless data
|
374
|
-
end
|
375
|
-
apply_style(data || theme, name, *args, empty: empty)
|
376
|
-
self
|
377
|
-
end
|
378
|
-
|
379
|
-
def script(group: nil, ref: nil)
|
380
|
-
if group || ref
|
381
|
-
(group && @script[:group][group.to_sym]) || (ref && @script[:ref][ref.to_sym])
|
382
|
-
else
|
383
|
-
@script[:build]
|
384
|
-
end
|
385
|
-
end
|
386
|
-
|
387
|
-
def env(key, equals: nil, ignore: nil)
|
388
|
-
ret = ENV.fetch("#{key}_#{main.gsub(/[^\w]/, '_').upcase}", ENV[key]).to_s
|
389
|
-
return ret == equals.to_s if equals
|
390
|
-
|
391
|
-
ret.empty? || as_a(ignore).any? { |val| ret == val.to_s } ? nil : ret
|
392
|
-
end
|
393
|
-
|
394
|
-
def read_manifest(*)
|
395
|
-
require 'rexml/document'
|
396
|
-
return unless (file = root_path('.repo/manifest.xml')).exist?
|
397
|
-
|
398
|
-
doc = REXML::Document.new(file.read)
|
399
|
-
doc.elements['manifest/include'].attributes['name']&.sub('.xml', '')
|
400
|
-
end
|
401
|
-
|
402
|
-
def root_path(*args)
|
403
|
-
root.join(*args)
|
404
|
-
end
|
405
|
-
|
406
|
-
def home_path(*args)
|
407
|
-
home.join(*args)
|
408
|
-
end
|
409
|
-
|
410
|
-
def find_base(obj)
|
411
|
-
Workspace.project_kind.find { |proj| obj.instance_of?(proj) }
|
412
|
-
end
|
413
|
-
|
414
|
-
def bool_match(val, pat)
|
415
|
-
case val
|
416
|
-
when nil, ''
|
417
|
-
pat.is_a?(::Regexp) ? pat : nil
|
418
|
-
when '0'
|
419
|
-
false
|
420
|
-
when '1'
|
421
|
-
true
|
422
|
-
else
|
423
|
-
Regexp.new(val)
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
def to_s
|
428
|
-
root.to_s
|
429
|
-
end
|
430
|
-
|
431
|
-
def inspect
|
432
|
-
"#<#{self.class}: #{main} => #{self}>"
|
433
|
-
end
|
434
|
-
|
435
|
-
def enabled?
|
436
|
-
repo? || @project.any? { |_, proj| proj.enabled? }
|
437
|
-
end
|
438
|
-
|
439
|
-
def repo?
|
440
|
-
!manifest_url.nil? && (empty?(root) || @override)
|
441
|
-
end
|
442
|
-
|
443
|
-
def multiple?(val = nil)
|
444
|
-
return multiple.include?(val) && invoked?(val) if val
|
445
|
-
|
446
|
-
::Rake::Task.tasks.any? { |item| item.already_invoked && multiple.include?(item.name) }
|
447
|
-
end
|
448
|
-
|
449
|
-
def sync?(val = nil)
|
450
|
-
return sync.include?(val) && invoked?(val) if val
|
451
|
-
|
452
|
-
::Rake::Task.tasks.any? { |item| item.already_invoked && sync.include?(item.name) }
|
453
|
-
end
|
454
|
-
|
455
|
-
def parallel?(val = nil)
|
456
|
-
return parallel.include?(val) && invoked?(val) if val
|
457
|
-
|
458
|
-
::Rake::Task.tasks.any? { |item| item.already_invoked && parallel.include?(item.name) }
|
459
|
-
end
|
460
|
-
|
461
|
-
def empty?(dir, repo: true)
|
462
|
-
return true if dir.empty? || (repo && dir.join('.repo').directory?)
|
463
|
-
return true if dir.children.size == 1 && dir.join(dir.children.first).to_s == __FILE__
|
464
|
-
|
465
|
-
dir.to_s == root.to_s && !env('REPO_ROOT').nil? && root.children.none? do |path|
|
466
|
-
path.directory? && !path.basename.to_s.start_with?('.') && path.to_s != home.to_s
|
467
|
-
end
|
468
|
-
end
|
469
|
-
|
470
|
-
def task_defined?(obj, key)
|
471
|
-
obj.is_a?(TASK_BASE[key])
|
472
|
-
end
|
473
|
-
|
474
|
-
def dev?(script: nil, pat: nil, global: nil)
|
475
|
-
with?(:dev, script: script, pat: pat, global: global || (pat.nil? && global.nil?))
|
476
|
-
end
|
477
|
-
|
478
|
-
def prod?(script: nil, pat: nil, global: false)
|
479
|
-
return false if global && (@script[:dev] == true || !@script[:prod])
|
480
|
-
|
481
|
-
with?(:prod, script: script, pat: pat, global: global)
|
482
|
-
end
|
483
|
-
|
484
|
-
protected
|
485
|
-
|
486
|
-
def confirm(msg, agree: 'Y', cancel: 'N', default: nil, attempts: 5, timeout: 15)
|
487
|
-
require 'readline'
|
488
|
-
require 'timeout'
|
489
|
-
Timeout.timeout(ENV.fetch('REPO_TIMEOUT', timeout).to_i) do
|
490
|
-
ret = false
|
491
|
-
begin
|
492
|
-
while (ch = Readline.readline(msg, true))
|
493
|
-
case ch.chomp.upcase
|
494
|
-
when agree
|
495
|
-
ret = true
|
496
|
-
break
|
497
|
-
when cancel
|
498
|
-
break
|
499
|
-
when ''
|
500
|
-
case default
|
501
|
-
when agree
|
502
|
-
ret = true
|
503
|
-
break
|
504
|
-
when cancel
|
505
|
-
break
|
506
|
-
end
|
507
|
-
end
|
508
|
-
attempts -= 1
|
509
|
-
exit 1 unless attempts >= 0
|
510
|
-
end
|
511
|
-
rescue Interrupt
|
512
|
-
puts
|
513
|
-
exit 0
|
514
|
-
else
|
515
|
-
ret
|
516
|
-
end
|
517
|
-
end
|
518
|
-
end
|
519
|
-
|
520
|
-
private
|
521
|
-
|
522
|
-
def script_command(task, val, group, ref)
|
523
|
-
if group
|
524
|
-
label = :group
|
525
|
-
items = as_a(group)
|
526
|
-
else
|
527
|
-
label = :ref
|
528
|
-
items = as_a(ref)
|
529
|
-
end
|
530
|
-
items.each { |name| (@script[label][name.to_sym] ||= {})[task] = val }
|
531
|
-
self
|
532
|
-
end
|
533
|
-
|
534
|
-
def repo_prompt(path)
|
535
|
-
return false unless path.directory?
|
536
|
-
|
537
|
-
confirm("#{log_title(:warn)} \"#{sub_style(path, :bold)}\" is not empty. Continue with installation? [y/N] ",
|
538
|
-
default: 'N')
|
539
|
-
end
|
540
|
-
|
541
|
-
def with?(state, script: nil, pat: nil, global: false)
|
542
|
-
if global
|
543
|
-
pat = @script[state] if pat.nil?
|
544
|
-
script ||= @script[:build]
|
545
|
-
end
|
546
|
-
pat.is_a?(::Regexp) ? pat.match?(script) : pat == true
|
547
|
-
end
|
548
|
-
end
|
549
|
-
end
|
550
|
-
end
|
551
|
-
|
552
|
-
Repo = Squared::Repo
|