squared 0.6.2 → 0.6.3

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.
@@ -0,0 +1,641 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Squared
6
+ module Workspace
7
+ module Project
8
+ module Support
9
+ class OptionPartition
10
+ include Common::Shell
11
+ extend Forwardable
12
+
13
+ OPT_NAME = /\A(?:(--)|-)((?(1)[^=\s-][^=\s]*|[^=\s-]))\z/
14
+ OPT_VALUE = /\A-{0,2}([^=\s-][^=\s]*)(?:=|\s+)(\S.*)\z/
15
+ OPT_SINGLE = /\A-([^=\s-])(.+)\z/
16
+ private_constant :OPT_NAME, :OPT_VALUE, :OPT_SINGLE
17
+
18
+ class << self
19
+ include Common::Format
20
+ include Shell
21
+ include Prompt
22
+
23
+ def append(target, *args, delim: false, escape: false, quote: true, strip: nil, force: true, double: false,
24
+ filter: nil, **)
25
+ return if (ret = args.flatten).empty?
26
+
27
+ target << '--' if delim && !target.include?('--')
28
+ if strip
29
+ pat, s = Array(strip)
30
+ ret.map! { |val| val.is_a?(String) ? val.gsub(pat, s || '') : val }
31
+ end
32
+ ret, err = ret.partition { |val| filter.match?(val.to_s) } if filter
33
+ if block_given?
34
+ out = []
35
+ err ||= []
36
+ ret.each do |val|
37
+ case (s = yield val)
38
+ when String
39
+ out << s
40
+ when NilClass, FalseClass
41
+ err << val
42
+ else
43
+ out << val
44
+ end
45
+ end
46
+ ret = out
47
+ end
48
+ if escape || quote
49
+ ret.map! do |val|
50
+ if opt?(val)
51
+ val
52
+ elsif escape
53
+ shell_escape(val, quote: quote, double: double)
54
+ else
55
+ shell_quote(val, force: force, double: double)
56
+ end
57
+ end
58
+ end
59
+ if target.is_a?(Set)
60
+ target.merge(ret)
61
+ else
62
+ target.concat(ret)
63
+ end
64
+ err || ret
65
+ end
66
+
67
+ def clear(target, opts, pass: true, styles: nil, **kwargs)
68
+ return if opts.empty?
69
+
70
+ kwargs[:subject] ||= target.first.stripext
71
+ kwargs[:hint] ||= 'unrecognized'
72
+ append(target, opts, delim: true) if kwargs.delete(:append)
73
+ warn log_message(Logger::WARN, opts.join(', '), pass: true, **kwargs)
74
+ exit 1 unless pass || confirm("Run? [#{sub_style(target, styles)}]", 'N')
75
+ end
76
+
77
+ def delete_key(target, *args, value: false, reverse: false, count: -1)
78
+ ret = []
79
+ args.each do |val|
80
+ next if (opts = target.grep(matchopt(val, value))).empty?
81
+
82
+ opts = opts.first(count) if count >= 0
83
+ opts.send(reverse ? :reverse_each : :each) { |key| target.delete(key) }
84
+ ret.concat(opts)
85
+ end
86
+ ret
87
+ end
88
+
89
+ def strip(val)
90
+ return [] unless val
91
+
92
+ val = shell_split val if val.is_a?(String)
93
+ val.map { |s| s.sub(OPT_SINGLE, '\1=\2').sub(OPT_VALUE, '\1=\2').sub(OPT_NAME, '\2') }.reject(&:empty?)
94
+ end
95
+
96
+ def select(list, bare: true, no: true, single: false, double: false)
97
+ ret = bare ? list.grep_v(/=/) : list.grep(/=/).map! { |val| val.split('=', 2).first }
98
+ ret.map! { |val| val.split('|', 2).last }
99
+ ret = ret.grep_v(/\Ano-/) unless no
100
+ return ret if single == double
101
+
102
+ ret.select { |val| single ? val.size == 1 : val.size > 1 }
103
+ end
104
+
105
+ def uniq!(list, pass = [])
106
+ keys = {}
107
+ list.each_with_index do |val, i|
108
+ j = val =~ OPT_VALUE ? $1 : val
109
+ (keys[j] ||= []) << i unless pass.include?(j)
110
+ end
111
+ data = keys.map { |item| item[1].size > 1 ? item[1][0..-2] : [] }.reject(&:empty?)
112
+ return if data.empty?
113
+
114
+ data.each { |key| key.each { |i| list[i] = nil } }
115
+ list.compact!
116
+ list
117
+ end
118
+
119
+ def arg?(target, *args, value: false, **)
120
+ r, s = args.partition { |val| val.is_a?(Regexp) }
121
+ r << matchopts(s, value) unless s.empty?
122
+ a = target.to_a
123
+ if (n = a.index('--'))
124
+ a = a[0..n]
125
+ end
126
+ r.any? { |pat| a.any?(pat) }
127
+ end
128
+
129
+ def opt?(val)
130
+ return false unless val.is_a?(String)
131
+
132
+ val.start_with?('-') && (OPT_NAME.match?(val) || OPT_VALUE.match?(val) || OPT_SINGLE.match?(val))
133
+ end
134
+
135
+ def pattern?(val)
136
+ val.match?(/(?:\A\^|\$\z)/) || val.match?(/(?:\.[*+]|\(\?:|\\[dsw]|\[.+\]|\{\d+,?\d*})/)
137
+ end
138
+
139
+ private
140
+
141
+ def matchopt(val, value = false)
142
+ /\A#{val.size == 1 ? shortopt(val) : longopt(val, value)}/
143
+ end
144
+
145
+ def matchopts(list, value = false)
146
+ a, b = Array(list).partition { |val| val.size == 1 || val.match?(OPT_SINGLE) }
147
+ return /\A#{shortopt(*a)}}/ if b.empty?
148
+ return /\A#{longopt(*b, value)}/ if a.empty?
149
+
150
+ /\A(?:#{shortopt(*a)}|#{longopt(*b, value)})/
151
+ end
152
+
153
+ def shortopt(*group)
154
+ group.map! { |s| Regexp.escape(s.delete_prefix('-')) }
155
+ "-(?:#{group.join('|')})(?:\\z|[^ =]| +[^ -])"
156
+ end
157
+
158
+ def longopt(*group, value)
159
+ group.map! { |s| Regexp.escape(s.delete_prefix('--')) }
160
+ "--(?:#{group.join('|')})(?:#{value ? '=[^ ]| +[^ -]' : '[= ]|\z'})"
161
+ end
162
+ end
163
+
164
+ attr_reader :target, :extras, :found, :errors, :values, :project, :path, :sep
165
+
166
+ def_delegators :@target, :+, :-, :<<, :any?, :none?, :include?, :add, :add?, :find, :find_all, :find_index,
167
+ :merge, :compact, :delete, :delete?, :delete_if, :grep, :grep_v, :inspect, :to_a, :to_s
168
+ def_delegators :@extras, :empty?, :each, :each_with_index, :partition, :dup, :first, :shift, :unshift,
169
+ :pop, :push, :concat, :index, :join, :detect, :map, :map!, :select, :select!, :slice, :slice!,
170
+ :reject, :size
171
+
172
+ def_delegator :@extras, :delete, :remove
173
+ def_delegator :@extras, :delete_at, :remove_at
174
+ def_delegator :@extras, :delete_if, :remove_if
175
+ def_delegator :@extras, :find_all, :detect_all
176
+ def_delegator :@extras, :find_index, :detect_index
177
+
178
+ def initialize(opts, list, target = Set.new, project: nil, path: nil, sep: '=', **kwargs, &blk)
179
+ @target = target.is_a?(Set) ? target : target.to_set
180
+ @project = project
181
+ @path = path || project&.path
182
+ @sep = sep
183
+ @errors = []
184
+ @found = []
185
+ parse(list, opts, **kwargs, &blk)
186
+ end
187
+
188
+ def parse(list, opts = extras, no: nil, single: nil, args: false, first: nil, underscore: nil, &blk)
189
+ @extras = []
190
+ @values = []
191
+ bare = []
192
+ e = []
193
+ b = []
194
+ m = []
195
+ p = []
196
+ q = []
197
+ qq = []
198
+ i = []
199
+ f = []
200
+ si = []
201
+ bl = []
202
+ ml = []
203
+ list.flat_map do |val|
204
+ x, y = val.split('|', 2)
205
+ if y
206
+ if (n = val.index('='))
207
+ x += val[n..-1]
208
+ end
209
+ [x, y]
210
+ else
211
+ x
212
+ end
213
+ end
214
+ .each do |val|
215
+ if (n = val.index('='))
216
+ flag = val[0, n]
217
+ case val[n.succ]
218
+ when 'e'
219
+ e << flag
220
+ when 'b'
221
+ b << flag
222
+ when 'm'
223
+ m << flag
224
+ when 'q'
225
+ qq << flag if val[n + 2] == 'q'
226
+ q << flag
227
+ when 'p'
228
+ p << flag
229
+ when 'i'
230
+ i << flag
231
+ when 'f'
232
+ f << flag
233
+ when 'n'
234
+ si << flag
235
+ when 'v'
236
+ @values << Regexp.escape(flag)
237
+ when '!'
238
+ bl << flag
239
+ when '+'
240
+ ml << flag
241
+ bare << flag
242
+ else
243
+ next
244
+ end
245
+ m << flag if val[n + 2] == 'm'
246
+ bare << flag if val.end_with?('?')
247
+ else
248
+ bare << val
249
+ end
250
+ end
251
+ no = (no || []).map { |val| (n = val.index('=')) ? val[0, n] : val }
252
+ bare.concat(no)
253
+ if underscore
254
+ tr = ->(a) { a.map { |val| val.tr('-', '_') } }
255
+ @values.concat(tr.call(@values))
256
+ bare.concat(tr.call(bare))
257
+ e.concat(tr.call(e))
258
+ b.concat(tr.call(b))
259
+ m.concat(tr.call(m))
260
+ p.concat(tr.call(p))
261
+ q.concat(tr.call(q))
262
+ qq.concat(tr.call(qq))
263
+ i.concat(tr.call(i))
264
+ f.concat(tr.call(f))
265
+ si.concat(tr.call(si))
266
+ bl.concat(tr.call(bl))
267
+ ml.concat(tr.call(ml))
268
+ no.concat(tr.call(no))
269
+ end
270
+ target.multiple = ml.map { |val| val.size == 1 ? "-#{val}" : "--#{val}" } if target.is_a?(JoinSet)
271
+ numtype = [
272
+ [i, /\A\d+\z/],
273
+ [f, /\A\d*(?:\.\d+)?\z/],
274
+ [si, /\A-?\d+\z/]
275
+ ].freeze
276
+ numcheck = ->(k, v) { numtype.any? { |flag, pat| flag.include?(k) && v.match?(pat) } }
277
+ skip = false
278
+ opts.each do |opt|
279
+ next skip = true if opt == '--'
280
+ next push opt if skip
281
+
282
+ if single&.match?(opt)
283
+ add "-#{opt}"
284
+ elsif bare.include?(opt)
285
+ add(opt.size == 1 ? "-#{opt}" : "--#{opt}")
286
+ elsif opt.start_with?(/no[-_]/) && no.include?(name = opt[3..-1])
287
+ add "--no-#{name}"
288
+ else
289
+ if opt =~ OPT_VALUE
290
+ key = $1
291
+ val = $2
292
+ merge = m.include?(key)
293
+ if e.include?(key)
294
+ add shell_option(key, val, merge: merge, sep: sep)
295
+ elsif q.include?(key)
296
+ add quote_option(key, val, double: qq.include?(key), merge: merge, sep: sep)
297
+ elsif p.include?(key)
298
+ if val.match?(/\A(["']).+\1\z/)
299
+ add shell_option(key, val, escape: false, merge: merge, sep: sep)
300
+ elsif path
301
+ add quote_option(key, path + val, merge: merge, sep: sep)
302
+ else
303
+ push opt
304
+ end
305
+ elsif b.include?(key) || (bl.include?(key) && %w[true false].include?(val)) || numcheck.call(key, val)
306
+ add basic_option(key, val, merge: merge, sep: sep)
307
+ elsif merge
308
+ add basic_option(key, val, merge: true, sep: sep)
309
+ else
310
+ push opt
311
+ end
312
+ opt = key
313
+ else
314
+ push opt
315
+ skip = true if args
316
+ end
317
+ skip = true if first&.any? { |s| s.is_a?(Regexp) ? opt.match?(s) : !opt.include?(s) }
318
+ end
319
+ end
320
+ @values = @values.empty? ? /\A\s+\z/ : /\A(#{@values.join('|')})#{sep}(.+)\z/m
321
+ @extras.each_with_index(&blk) if block_given?
322
+ self
323
+ end
324
+
325
+ def swap(opts = nil, &blk)
326
+ unless opts
327
+ opts = found
328
+ @found = []
329
+ end
330
+ opts.sort!(&blk) if block_given?
331
+ @extras = opts
332
+ self
333
+ end
334
+
335
+ def append(*args, **kwargs, &blk)
336
+ args = extras if args.empty?
337
+ out = OptionPartition.append(target, *args, **kwargs, &blk)
338
+ errors.concat(out) if out && (block_given? || kwargs[:filter])
339
+ self
340
+ end
341
+
342
+ def append_any(*args, quote: true, **kwargs)
343
+ (args.empty? ? extras : args.flatten).each do |val|
344
+ val = yield val if block_given?
345
+ next unless val.is_a?(String)
346
+
347
+ if exist?(val)
348
+ add_path(val, **kwargs)
349
+ elsif quote
350
+ add_quote(val, **kwargs)
351
+ else
352
+ add val
353
+ end
354
+ end
355
+ self
356
+ end
357
+
358
+ def delete_key(*args, **kwargs)
359
+ OptionPartition.delete_key(target, *args, **kwargs)
360
+ self
361
+ end
362
+
363
+ def values_of(*args, strict: true, first: false, last: false)
364
+ eq, s = strict ? [sep, '[^ ]+'] : ['(?:=| +)', '[^-][^ ]*']
365
+ g = ["\"((?:[^\"]|(?<=\\\\)\"(?!$#{'| ' if windows?}))*)\""]
366
+ g << "'((?:[^']|'\\\\'')*)'" unless windows?
367
+ g << "(#{s})"
368
+ args.map! do |opt|
369
+ if opt.size == 1
370
+ /(?:\A| )-#{opt} ?([^ ]+)/
371
+ else
372
+ /(?:\A| )--#{opt + eq}(?:#{g.join('|')})/
373
+ end
374
+ end
375
+ ret = []
376
+ target.each do |opt|
377
+ args.each do |pat|
378
+ next unless opt =~ pat
379
+
380
+ ret << ($1 || $2 || $3)
381
+ break
382
+ end
383
+ end
384
+ return ret unless first || last
385
+
386
+ if last.is_a?(Numeric)
387
+ ret.last(last)
388
+ elsif last
389
+ ret.last
390
+ else
391
+ first.is_a?(Numeric) ? ret.first(first) : ret.first
392
+ end
393
+ end
394
+
395
+ def uniq(list)
396
+ ignore = map { |val| nameonly(val) }
397
+ list.reject { |val| ignore.include?(s = nameonly(val)) || any?(OptionPartition.send(:matchopt, s)) }
398
+ end
399
+
400
+ def clear(opts = nil, errors: false, **kwargs)
401
+ styles = project.theme[:inline] if project
402
+ if errors
403
+ OptionPartition.clear(target, @errors, styles: styles, **kwargs)
404
+ @errors.clear
405
+ return self unless opts
406
+ end
407
+ opts ||= extras
408
+ OptionPartition.clear(target, opts - found, styles: styles, **kwargs)
409
+ opts.clear
410
+ self
411
+ end
412
+
413
+ def adjoin(*args, with: nil, start: false)
414
+ index = -1
415
+ temp = compact
416
+ if with
417
+ pat = case with
418
+ when String, Symbol
419
+ /\A#{Regexp.escape(with)}\z/
420
+ when Array
421
+ OptionPartition.send(:matchopts, with)
422
+ else
423
+ with
424
+ end
425
+ temp.each_with_index do |val, i|
426
+ if val.to_s.match?(pat)
427
+ index = i + (start.is_a?(Numeric) ? start : 1)
428
+ break
429
+ end
430
+ end
431
+ else
432
+ temp.each_with_index do |val, i|
433
+ if index == 0
434
+ next unless val.is_a?(String) && val.start_with?('-')
435
+
436
+ index = i
437
+ break
438
+ elsif i > 0 && !val.to_s.start_with?('-')
439
+ if start
440
+ index = i + (start.is_a?(Numeric) ? start : 1)
441
+ break
442
+ end
443
+ index = 0
444
+ end
445
+ end
446
+ end
447
+ if index > 0
448
+ if args.empty?
449
+ args = dup
450
+ reset
451
+ else
452
+ args.each { |val| remove val }
453
+ end
454
+ args = temp[0...index] + args + temp[index..-1]
455
+ target.clear
456
+ end
457
+ merge args
458
+ self
459
+ end
460
+
461
+ def add_path(*args, force: true, double: false, **kwargs)
462
+ if args.empty?
463
+ args = select { |val| val.is_a?(String) }
464
+ args.map! { |val| path + val } if path
465
+ append(args, force: force, **kwargs)
466
+ else
467
+ add shell_quote(path ? path.join(*args) : File.join(*args), option: false, force: force, double: double)
468
+ end
469
+ self
470
+ end
471
+
472
+ def add_quote(*args, **kwargs)
473
+ merge(args.compact
474
+ .map! { |val| val == '--' || OptionPartition.opt?(val) ? val : shell_quote(val, **kwargs) })
475
+ self
476
+ end
477
+
478
+ def add_option(flag, val = nil, **kwargs)
479
+ add shell_option(flag, val, **kwargs)
480
+ self
481
+ end
482
+
483
+ def add_first(fallback = nil, prefix: nil, path: false, quote: false, reverse: false, expect: false, **kwargs)
484
+ val = (reverse ? pop : shift) || fallback
485
+ if val
486
+ val.delete_prefix!(prefix) if prefix && val.is_a?(String)
487
+ unless block_given? && !(val = yield val).is_a?(String)
488
+ if path
489
+ add_path(val, **kwargs)
490
+ elsif quote
491
+ add_quote(val, **kwargs)
492
+ else
493
+ add val
494
+ end
495
+ end
496
+ elsif expect
497
+ raise(expect.is_a?(String) ? expect : 'no value to add')
498
+ end
499
+ self
500
+ end
501
+
502
+ def last(val = nil, &blk)
503
+ unless block_given?
504
+ case val
505
+ when NilClass
506
+ return extras.last
507
+ when Numeric
508
+ return extras.last(val)
509
+ when String, Array, Regexp
510
+ val = OptionPartition.send(:matchopts, val) unless val.is_a?(Regexp)
511
+ blk = proc { |s| s&.match?(val) }
512
+ else
513
+ raise TypeError, "unknown: #{val}"
514
+ end
515
+ end
516
+ ret = find_all(&blk)
517
+ unless ret.empty?
518
+ ret = case val
519
+ when NilClass
520
+ ret.first(1)
521
+ when Numeric
522
+ ret.first(val)
523
+ else
524
+ ret
525
+ end
526
+ ret.each do |opt|
527
+ delete opt
528
+ add opt
529
+ end
530
+ end
531
+ val.nil? ? ret.first : ret
532
+ end
533
+
534
+ def splice(*exclude, quote: true, delim: true, path: false, pattern: false, &blk)
535
+ found, other = if block_given?
536
+ partition(&blk)
537
+ elsif exclude.first.is_a?(Symbol)
538
+ partition(&exclude.first)
539
+ else
540
+ partition do |val|
541
+ next false if pattern && OptionPartition.pattern?(val)
542
+
543
+ exclude.none? { |pat| val.match?(Regexp.new(pat)) }
544
+ end
545
+ end
546
+ unless found.empty?
547
+ add '--' if delim
548
+ extras.clear
549
+ concat other
550
+ if path
551
+ found.each { |val| add_path(val) }
552
+ else
553
+ found.map! { |val| shell_quote(val) } if quote
554
+ merge found
555
+ end
556
+ end
557
+ self
558
+ end
559
+
560
+ def reset(errors: false)
561
+ extras.clear
562
+ found.clear
563
+ clear(errors: true) if errors
564
+ self
565
+ end
566
+
567
+ def append?(key, val = nil, type: nil, force: false, sep: '=', **kwargs)
568
+ return false unless force || !arg?(key)
569
+
570
+ val = yield self if block_given?
571
+ return false unless val
572
+
573
+ type ||= :quote if kwargs.empty?
574
+ add case type
575
+ when :quote
576
+ quote_option(key, val, sep: sep)
577
+ when :basic
578
+ basic_option(key, val, sep: sep)
579
+ else
580
+ shell_option(key, val, sep: sep, **kwargs)
581
+ end
582
+ true
583
+ end
584
+
585
+ def arg?(*args, **kwargs)
586
+ OptionPartition.arg?(target, *args, **kwargs)
587
+ end
588
+
589
+ def exist?(*args, add: false, first: false, last: false, glob: false)
590
+ return with_glob?(File.join(*args), glob) unless args.empty?
591
+
592
+ if first || last
593
+ return false unless (val = first ? self.first : self.last)
594
+
595
+ with_glob?(val, glob).tap do |ret|
596
+ next unless add && ret
597
+
598
+ add_first(path: true, reverse: !first)
599
+ end
600
+ else
601
+ each_with_index do |val, i|
602
+ next unless with_glob?(val, glob)
603
+
604
+ if add
605
+ remove_at i
606
+ add_path val
607
+ end
608
+ return true
609
+ end
610
+ false
611
+ end
612
+ end
613
+
614
+ def uniq!(list)
615
+ unless (list = uniq(list)).empty?
616
+ concat list
617
+ self
618
+ end
619
+ end
620
+
621
+ private
622
+
623
+ def nameonly(val)
624
+ val[OPT_VALUE, 1] || val
625
+ end
626
+
627
+ def with_glob?(val, glob = true)
628
+ return false unless path && val.is_a?(String) && !val.empty?
629
+
630
+ path.join(val).exist? || (glob && !path.glob(val).empty?)
631
+ end
632
+
633
+ def windows?
634
+ require 'rake'
635
+ Rake::Win32.windows?
636
+ end
637
+ end
638
+ end
639
+ end
640
+ end
641
+ end
@@ -7,6 +7,5 @@ module Squared
7
7
  end
8
8
  end
9
9
 
10
- require_relative 'project/support'
11
10
  require_relative 'project/base'
12
11
  require_relative 'project/git'