squared 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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