squared 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d5c87e8fd0e459e55e900839ba9f5f43e51ee1990d1fa103f9de264ae94c4e1
4
- data.tar.gz: 1e38b9df0a7d3f2b48d6912b880d1c2d593dcf04a1942a01791b84ea8b9e023c
3
+ metadata.gz: 5fb2101a0fb0c5a8f651bf471b44a5dc3be21c3b87d6e53f708d752b6e8c8ffd
4
+ data.tar.gz: eb322ca0cab89e5f41f719e06e182120efd96280f88a5f2625f3954331500a19
5
5
  SHA512:
6
- metadata.gz: f4b9df6f9d39a24bd1db9e056ab4b5864e36a6d5daf46403881845e3d1d725033b31a4c9d11274595a74c59dbf13ac77845e96a0c3e2f74c09317a3a1f123a62
7
- data.tar.gz: fcd0370d7ba7b6b590c9fec4a411a302dde15c8124f832aa5ed1a1e58cef209481ac9d595a5d8d385fefce87a8dc0e53a9208b0da6916c00d3a3e8f853748813
6
+ metadata.gz: a6947106acb6d33789f3ae79a84e87c3119b799f556a8570920904338b3955b795578f392860fce83260af5b1c17159ba4c64e9f4eeeaed6b5886e06dfe12a06
7
+ data.tar.gz: dd9416a0457a2ead233f32c9592b2061e569161d54482d4ab949fd7acb6ceb6139c26329a4fdb6ea1853ab6f840ab145e17f4b58a386bd68eb066c14ea3f85cd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2] - 2024-12-23
4
+
5
+ ### Added
6
+
7
+ - Git command tag was implemented.
8
+
9
+ ### Deprecated
10
+
11
+ - Repo static method empty? was relocated into Application.
12
+
13
+ ### Fixed
14
+
15
+ - Namespace prefix was sometimes missing or duplicated.
16
+ - Silent operation used undefined variable.
17
+ - Unnecessary shell option escapes were removed.
18
+ - Ruby command outdated did not display in order when threaded.
19
+ - Regexp uses \A|\z format and not ^|$ for standard matches.
20
+ - Rake being called by a project did not escape options.
21
+ - Workspace did not check base project for Windows filename.
22
+ - Regexp for SemVer did not recognize package names.
23
+
24
+ ## [0.1.1] - 2024-12-14
25
+
26
+ ### Added
27
+
28
+ - Node package manager update command was implemented.
29
+ - Git pull and fetch retrieve options were expanded.
30
+
31
+ ### Changed
32
+
33
+ - Git show does not require an object argument.
34
+ - Project base class is implemented through Application.
35
+
36
+ ### Fixed
37
+
38
+ - Listing tasks did not check for rake -C option availability.
39
+ - Listing gems did not check for gem -C option availability.
40
+ - Disabled extension tasks by individual project were being created.
41
+ - Git commit did not locate origin from nested branches.
42
+ - Project clean command used incompatible folder delete option.
43
+ - Rake did not set original dir when calling itself.
44
+ - Rake did not set original rakefile when calling itself.
45
+ - Extended tasks were not associated to their supporting class method.
46
+
3
47
  ## [0.1.0] - 2024-12-7
4
48
 
5
49
  ### Added
@@ -27,5 +71,7 @@
27
71
 
28
72
  - Changelog was created.
29
73
 
74
+ [0.1.2]: https://github.com/anpham6/squared/releases/tag/v0.1.2-ruby
75
+ [0.1.1]: https://github.com/anpham6/squared/releases/tag/v0.1.1-ruby
30
76
  [0.1.0]: https://github.com/anpham6/squared/releases/tag/v0.1.0-ruby
31
77
  [0.0.12]: https://github.com/anpham6/squared/releases/tag/v0.0.12-ruby
data/README.md CHANGED
@@ -108,7 +108,7 @@ Workspace management uses [Ruby](https://www.ruby-lang.org/en/documentation/inst
108
108
  mkdir workspaces
109
109
  cd workspaces # REPO_ROOT
110
110
 
111
- wget https://raw.githubusercontent.com/anpham6/squared/master/Rakefile
111
+ wget https://unpkg.com/squared/Rakefile
112
112
 
113
113
  rake -T # List tasks
114
114
 
data/README.ruby.md CHANGED
@@ -37,17 +37,23 @@ Projects from any accessible folder can be added relative to the parent director
37
37
  require "squared"
38
38
 
39
39
  require "squared/workspace"
40
- require "squared/workspace/repo" # Optional
41
- require "squared/workspace/project/node" #
42
- require "squared/workspace/project/python" #
43
- require "squared/workspace/project/ruby" #
40
+ require "squared/workspace/repo" # Optional
41
+ require "squared/workspace/project/node" #
42
+ require "squared/workspace/project/python" #
43
+ require "squared/workspace/project/ruby" #
44
44
  # OR
45
- require "squared/app" # All workspace related modules
45
+ require "squared/app" # All workspace related modules
46
46
 
47
47
  # NODE_ENV = production
48
- # REPO_ROOT = /workspaces
49
- # REPO_HOME = /workspaces/squared
50
- # rake = /workspaces/squared/Rakefile
48
+
49
+ # REPO_ROOT = /workspaces #
50
+ # REPO_HOME = /workspaces/squared # Dir.pwd
51
+ # rake = /workspaces/squared/Rakefile # main?
52
+ # OR
53
+ # REPO_ROOT = /workspaces # Dir.pwd
54
+ # rake = /workspaces/Rakefile #
55
+ # REPO_HOME = /workspaces/squared # main: "squared"
56
+
51
57
  # pathname = /workspaces/pathname
52
58
  # optparse = /workspaces/optparse
53
59
  # log = /workspaces/logger
@@ -9,7 +9,7 @@ module Squared
9
9
  extend Forwardable
10
10
 
11
11
  def self.to_s
12
- super.match(/[^:]+$/)[0]
12
+ super.match(/[^:]+\z/)[0]
13
13
  end
14
14
 
15
15
  def_delegators :@data, :+, :each, :each_with_index, :entries, :to_a, :include?
@@ -31,7 +31,7 @@ module Squared
31
31
 
32
32
  class JoinSet < Set
33
33
  def self.to_s
34
- super.match(/[^:]+$/)[0]
34
+ super.match(/[^:]+\z/)[0]
35
35
  end
36
36
 
37
37
  def initialize(data = [], delim: ' ')
@@ -46,7 +46,7 @@ module Squared
46
46
  if pat && index != 0
47
47
  return val unless (data = pat.match(val))
48
48
 
49
- ret = index == -1 ? data.to_a[1..-1] : data[index]
49
+ ret = index == -1 ? data.to_a.drop(1) : data[index]
50
50
  else
51
51
  ret = val
52
52
  index = 0
@@ -170,7 +170,7 @@ module Squared
170
170
  args = args.map(&:to_s)
171
171
  if args.size > 1
172
172
  title = log_title(level, color: false)
173
- sub = { pat: /^(#{title})(.+)$/, styles: __get__(:theme)[:logger][log_sym(level)] } if color
173
+ sub = { pat: /\A(#{title})(.+)\z/m, styles: __get__(:theme)[:logger][log_sym(level)] } if color
174
174
  emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub)
175
175
  else
176
176
  msg = [log_title(level, color: color)]
@@ -235,8 +235,8 @@ module Squared
235
235
  sub.each { |h| s = sub_style(s, **h) }
236
236
  s = "| #{s} |"
237
237
  if border
238
- s = sub_style(s, pat: /^(\|)(.+)$/m, styles: border)
239
- s = sub_style(s, pat: /^(.+)(\|)$/m, styles: border, index: 2)
238
+ s = sub_style(s, pat: /\A(\|)(.+)\z/m, styles: border)
239
+ s = sub_style(s, pat: /\A(.+)(\|)\z/m, styles: border, index: 2)
240
240
  end
241
241
  s
242
242
  end
@@ -8,8 +8,8 @@ module Squared
8
8
  def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 5, timeout: 15)
9
9
  require 'readline'
10
10
  require 'timeout'
11
- agree = /^#{agree}$/i if agree.is_a?(::String)
12
- cancel = /^#{cancel}$/i if cancel.is_a?(::String)
11
+ agree = /\A#{agree}\z/i if agree.is_a?(::String)
12
+ cancel = /\A#{cancel}\z/i if cancel.is_a?(::String)
13
13
  Timeout.timeout(timeout) do
14
14
  begin
15
15
  while (ch = Readline.readline(msg, true))
@@ -8,38 +8,51 @@ module Squared
8
8
  module Shell
9
9
  module_function
10
10
 
11
- def shell_escape(val, quote: false)
12
- val = val.to_s
13
- return ::Shellwords.escape(val) unless ::Rake::Win32.windows?
11
+ def shell_escape(val, quote: false, force: false)
12
+ if (data = /\A(--?[^= ]+)((=|\s+)(["'])?(.+?)(["'])?)?\z/m.match(val = val.to_s))
13
+ return val unless data[2]
14
14
 
15
- quote ? shell_quote(val, force: false) : val
15
+ join = ->(opt) { data[1] + data[3] + shell_quote(opt) }
16
+ if data[4] == data[6]
17
+ data[4] ? val : join.(data[5])
18
+ else
19
+ join.("#{data[4]}#{data[5]}#{data[6]}")
20
+ end
21
+ elsif Rake::Win32.windows?
22
+ quote ? shell_quote(val, force: force) : val
23
+ else
24
+ Shellwords.escape(val)
25
+ end
16
26
  end
17
27
 
18
28
  def shell_quote(val, force: true)
19
- ret = val.to_s.strip
20
- return ret if (!force && !ret.include?(' ')) || ret =~ /(?:^|\S=|[^=]\s+)(["']).+\1$/m
29
+ val = val.to_s
30
+ return val if (!force && !val.include?(' ')) || val =~ /(?:^|\S=|[^=]\s+)(["']).+\1\z/m
21
31
 
22
- ::Rake::Win32.windows? ? "\"#{double_quote(ret)}\"" : "'#{single_quote(ret)}'"
32
+ Rake::Win32.windows? ? "\"#{double_quote(val)}\"" : "'#{single_quote(val)}'"
23
33
  end
24
34
 
25
- def shell_split(val, quote: false, join: false)
26
- ret = ::Shellwords.split(val).map do |opt|
27
- if (data = /^(--?[^= ]+)(=|\s+)?(["'])?(.+?)\3?$/m.match(opt))
28
- next opt unless data[2] && !data[3]
35
+ def shell_split(val, quote: false, join: nil)
36
+ val = Shellwords.split(val).map { |opt| shell_escape(opt, quote: quote) }
37
+ return val unless join
29
38
 
30
- data[1] + data[2] + shell_quote(data[4], force: !::Rake::Win32.windows?)
31
- else
32
- shell_escape(opt, quote: quote)
33
- end
34
- end
35
- join ? ret.join(' ') : ret
39
+ val.join(join.is_a?(::String) ? join : ' ')
40
+ end
41
+
42
+ def shell_option(flag, val = nil, quote: false, escape: true)
43
+ "--#{flag}#{if val
44
+ "=#{if escape
45
+ shell_escape(val, quote: quote)
46
+ else
47
+ quote ? shell_quote(val) : val
48
+ end}"
49
+ end}"
36
50
  end
37
51
 
38
52
  def fill_option(val)
39
- return "-#{val}" if val.size == 1 || val =~ /^[a-z]\d+$/i
53
+ return "-#{val}" if val.size == 1 || val =~ /\A[a-z]\d+\z/i
40
54
 
41
- val = "--#{val}" unless val.start_with?('-')
42
- shell_escape(val).sub('\\=', '=')
55
+ shell_escape(val.start_with?('-') ? val : "--#{val}")
43
56
  end
44
57
 
45
58
  def single_quote(val)
@@ -9,7 +9,7 @@ module Squared
9
9
  module_function
10
10
 
11
11
  def shell(*args, **kwargs)
12
- if RUBY_VERSION =~ /^2\.[0-5]\./
12
+ if RUBY_VERSION < '2.6'
13
13
  exception = kwargs.delete(:exception)
14
14
  ret = system(*args, **kwargs)
15
15
  return ret if ret || !exception
@@ -12,7 +12,7 @@ module Squared
12
12
  include Rake::DSL
13
13
 
14
14
  def self.to_s
15
- super.match(/[^:]+$/)[0]
15
+ super.match(/[^:]+\z/)[0]
16
16
  end
17
17
 
18
18
  attr_reader :main, :name, :project, :theme
@@ -199,17 +199,18 @@ module Squared
199
199
  title = Pathname.new(file)
200
200
  .realpath
201
201
  .to_s
202
- .sub(Regexp.new("^#{Regexp.escape(File.join(Dir.pwd, ''))}"), '')
202
+ .sub(Regexp.new("\\A#{Regexp.escape(File.join(Dir.pwd, ''))}"), '')
203
203
  emphasize(lines, title: title, sub: unless stdin?
204
204
  [
205
- { pat: /^((?:[^:]|(?<! ):(?! ))+)$/, styles: theme[:banner] },
206
- { pat: /^(.*?)(<[^>]+>)(.+)$/m, styles: theme[:undefined], index: 2 },
207
- { pat: /^(.+)( : (?!undefined).+)$/m, styles: theme[:key] },
208
- { pat: /^(.+ : )(-?[\d.]+)(\s*)$/m, styles: theme[:number], index: 2 },
209
- { pat: /^(.+ : ")(.+)("\s*)$/m, styles: theme[:string], index: 2 },
210
- { pat: /^(.+ : \{)(.+)(\}\s*)$/m, styles: theme[:hash], index: 2 },
211
- { pat: /^(.+ : \[)(.+)(\]\s*)$/m, styles: theme[:array], index: 2 },
212
- { pat: /^(.+ : (?!undefined))([^"\[{].*)$/m, styles: theme[:value],
205
+ { pat: /\A((?:[^:]|(?<! ):(?! ))+)\z/, styles: theme[:banner] },
206
+ { pat: /\A(.*?)(<[^>]+>)(.+)\z/m, styles: theme[:undefined], index: 2 },
207
+ { pat: /\A(.+)( : (?!undefined).+)\z/m, styles: theme[:key] },
208
+ { pat: /\A(.+ : )(-?[\d.]+)(\s*)\z/m, styles: theme[:number],
209
+ index: 2 },
210
+ { pat: /\A(.+ : ")(.+)("\s*)\z/m, styles: theme[:string], index: 2 },
211
+ { pat: /\A(.+ : \{)(.+)(\}\s*)\z/m, styles: theme[:hash], index: 2 },
212
+ { pat: /\A(.+ : \[)(.+)(\]\s*)\z/m, styles: theme[:array], index: 2 },
213
+ { pat: /\A(.+ : (?!undefined))([^"\[{].*)\z/m, styles: theme[:value],
213
214
  index: 2 }
214
215
  ]
215
216
  end)
@@ -261,6 +262,8 @@ module Squared
261
262
  end
262
263
 
263
264
  def format_desc(command, *ext, exist: exist?)
265
+ return unless Rake::TaskManager.record_task_metadata
266
+
264
267
  val = "#{ext.first}[#{exist ? '' : "file?=#{File.basename(main)}.#{ext.last},"}keys+]"
265
268
  message(@prefix, *name.split(':'), command, val, empty: true)
266
269
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.2'
5
5
  end
@@ -18,12 +18,19 @@ module Squared
18
18
  private_constant :SCRIPT_OBJ
19
19
 
20
20
  class << self
21
- def implement(*objs)
21
+ def implement(*objs, base: false)
22
+ return if base && objs.size > 1
23
+
22
24
  objs.each do |obj|
23
- next unless obj < impl_project
25
+ next unless base || obj < impl_project
24
26
 
25
- kind_project.unshift(obj)
26
- obj.tasks&.each { |task| impl_series.add(task, obj) }
27
+ if base
28
+ @impl_project = obj
29
+ impl_series.base_set(obj)
30
+ else
31
+ kind_project.unshift(obj)
32
+ obj.tasks&.each { |task| impl_series.add(task, obj) }
33
+ end
27
34
  if (args = obj.batchargs)
28
35
  impl_series.batch(*args)
29
36
  end
@@ -49,7 +56,7 @@ module Squared
49
56
  end
50
57
 
51
58
  def to_s
52
- super.match(/[^:]+$/)[0]
59
+ super.match(/[^:]+\z/)[0]
53
60
  end
54
61
 
55
62
  attr_reader :kind_project
@@ -63,8 +70,14 @@ module Squared
63
70
  def initialize(home = Dir.pwd, *, main: nil, prefix: nil,
64
71
  verbose: ARG[:VERBOSE], common: ARG[:COMMON], pipe: ARG[:PIPE], exception: ARG[:FAIL], **)
65
72
  @home = Pathname.new(home).realdirpath
73
+ basename = @home.basename.to_s
74
+ if main
75
+ @main = main.to_s.freeze
76
+ @home = @home.join(@main) unless @main == basename || (windows? && @main.downcase == basename.downcase)
77
+ else
78
+ @main = basename.freeze
79
+ end
66
80
  @root = @home.parent
67
- @main = (main || @home.basename).to_s.freeze
68
81
  @prefix = prefix
69
82
  @series = Application.impl_series.new(self)
70
83
  @project = {}
@@ -315,7 +328,7 @@ module Squared
315
328
 
316
329
  def task_name(val, desc: false)
317
330
  ret = @prefix ? task_join(@prefix, val) : val.to_s
318
- desc ? ret.split(':').join(ARG[:SPACE]) : ret
331
+ desc ? message(*ret.split(':')) : ret
319
332
  end
320
333
 
321
334
  def task_namespace(val, first: false)
@@ -376,7 +389,7 @@ module Squared
376
389
 
377
390
  return ret
378
391
  end
379
- @script[:ref!][:_] || SCRIPT_OBJ
392
+ @script[:ref!][:''] || SCRIPT_OBJ
380
393
  end
381
394
 
382
395
  def script_get(*args, group: nil, ref: nil)
@@ -402,7 +415,7 @@ module Squared
402
415
  return ret if group && (ret = @banner[:group][group.to_sym])
403
416
 
404
417
  ref.reverse_each { |val| return ret if (ret = @banner[:ref][val]) }
405
- @banner[:ref][:_]
418
+ @banner[:ref][:'']
406
419
  end
407
420
 
408
421
  def enabled?
@@ -437,6 +450,10 @@ module Squared
437
450
  !(proj = find(home)).nil? && proj.enabled?
438
451
  end
439
452
 
453
+ def windows?
454
+ Rake::Win32.windows?
455
+ end
456
+
440
457
  def rootpath(*args)
441
458
  root.join(*args)
442
459
  end
@@ -498,7 +515,21 @@ module Squared
498
515
  elsif ref
499
516
  as_a(ref).each { |val| @script[:ref!][val.to_sym] = data }
500
517
  else
501
- @script[:ref!][:_] = data
518
+ @script[:ref!][:''] = data
519
+ end
520
+ end
521
+
522
+ def root?(path, pass: [])
523
+ return false unless path.directory?
524
+
525
+ case path.children.size
526
+ when 0
527
+ true
528
+ when 1
529
+ target = path.children.first
530
+ target.to_s == __FILE__ || pass.any? { |val| val == target.basename.to_s }
531
+ else
532
+ false
502
533
  end
503
534
  end
504
535
 
@@ -14,8 +14,8 @@ module Squared
14
14
  include Utils
15
15
  include Rake::DSL
16
16
 
17
- VAR_SET = %i[parent global envname dependfile theme run script env depend graph doc test copy clean].freeze
18
- SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?/.freeze
17
+ VAR_SET = %i[parent global envname dependfile theme run script env].freeze
18
+ SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
19
19
  private_constant :VAR_SET, :SEM_VER
20
20
 
21
21
  class << self
@@ -25,7 +25,7 @@ module Squared
25
25
  def bannerargs(*); end
26
26
 
27
27
  def tasks
28
- [].freeze
28
+ %i[build depend graph doc test copy clean].freeze
29
29
  end
30
30
 
31
31
  def as_path(val)
@@ -46,7 +46,7 @@ module Squared
46
46
  end
47
47
 
48
48
  def to_s
49
- super.match(/[^:]+$/)[0]
49
+ super.match(/[^:]+\z/)[0]
50
50
  end
51
51
  end
52
52
 
@@ -264,9 +264,9 @@ module Squared
264
264
  val
265
265
  end
266
266
  emphasize(out, title: path, right: true, border: borderstyle, sub: [
267
- { pat: /^(#{Regexp.escape(path.to_s)})(.*)$/, styles: theme[:header] },
268
- { pat: /^(#{Regexp.escape(name)})(.*)$/, styles: theme[:active] },
269
- { pat: /^(.+ )(\()(\d+)(\))(.*)$/, styles: theme[:inline], index: 3 }
267
+ { pat: /\A(#{Regexp.escape(path.to_s)})(.*)\z/, styles: theme[:header] },
268
+ { pat: /\A(#{Regexp.escape(name)})(.*)\z/, styles: theme[:active] },
269
+ { pat: /\A(.+ )(\()(\d+)(\))(.*)\z/, styles: theme[:inline], index: 3 }
270
270
  ])
271
271
  end
272
272
  end
@@ -288,10 +288,10 @@ module Squared
288
288
 
289
289
  def add(path, name = nil, **kwargs, &blk)
290
290
  checkdir = lambda do |val|
291
- if val.directory? && val.exist?
291
+ if val.directory? && !val.empty?
292
292
  true
293
293
  else
294
- log.warn "workspace \"#{val}\" (not found)"
294
+ log.warn "workspace \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
295
295
  false
296
296
  end
297
297
  end
@@ -383,7 +383,7 @@ module Squared
383
383
  next unless dir.directory?
384
384
 
385
385
  log.warn "rm -rf #{dir}"
386
- dir.rmtree(verbose: true)
386
+ dir.rmtree
387
387
  else
388
388
  files = val.include?('*') ? Dir[basepath(val)] : [basepath(val)]
389
389
  files.each do |file|
@@ -419,7 +419,7 @@ module Squared
419
419
  def variable_set(key, *val, **kwargs)
420
420
  if variables.include?(key)
421
421
  case key
422
- when :run
422
+ when :build, :run
423
423
  run_set(*val, **kwargs)
424
424
  when :script
425
425
  script_set(*val, **kwargs)
@@ -546,18 +546,17 @@ module Squared
546
546
  puts_oe(*args, pipe: pipe)
547
547
  end
548
548
 
549
- def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, **)
549
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, **)
550
550
  cmd = session_done(cmd)
551
551
  log.info cmd
552
552
  begin
553
- if cmd =~ /^\S+:(\S+:?)+$/ && workspace.task_defined?(cmd)
554
- print_item if sync
553
+ if cmd =~ /\A[^:]+:[^:]/ && workspace.task_defined?(cmd)
555
554
  log.warn "ENV was discarded: #{var}" if var
556
555
  task_invoke(cmd, exception: exception, warning: warning?)
557
556
  else
558
557
  print_item format_banner(cmd, banner: banner) if sync
559
558
  args = var.is_a?(Hash) ? [var, cmd] : [cmd]
560
- shell(*args, chdir: path, exception: exception)
559
+ shell(*args, chdir: chdir, exception: exception)
561
560
  end
562
561
  rescue StandardError => e
563
562
  log.error e
@@ -750,6 +749,8 @@ module Squared
750
749
  end
751
750
 
752
751
  def format_desc(action, flag, opts = nil, req: nil, arg: 'opts*')
752
+ return unless Rake::TaskManager.record_task_metadata
753
+
753
754
  opts = "#{arg}=#{opts.join(',')}" if opts.is_a?(Array)
754
755
  out = [@desc]
755
756
  if flag
@@ -772,7 +773,7 @@ module Squared
772
773
  end
773
774
  if verbose
774
775
  out = []
775
- out << cmd.sub(/^\S+/, &:upcase) if data[:command]
776
+ out << cmd.sub(/\A\S+/, &:upcase) if data[:command]
776
777
  data[:order].each do |val|
777
778
  if val.is_a?(Array)
778
779
  s = ' '
@@ -794,7 +795,7 @@ module Squared
794
795
  out << val.to_s
795
796
  end
796
797
  print_banner(*out, styles: data[:styles], border: data[:border], client: client)
797
- elsif multiple && workspace.series.multiple?
798
+ elsif workspace.series.multiple?
798
799
  "## #{__send__(data[:order].first || :path)} ##"
799
800
  end
800
801
  end
@@ -819,10 +820,10 @@ module Squared
819
820
  end
820
821
  if from
821
822
  out << from
822
- pat = /^(#{Regexp.escape(out.last)})(.*)$/
823
+ pat = /\A(#{Regexp.escape(out.last)})(.*)\z/m
823
824
  end
824
825
  else
825
- pat = /^(\s*\d+\.)(.+)$/
826
+ pat = /\A(\s*\d+\.)(.+)\z/m
826
827
  end
827
828
  sub = [headerstyle]
828
829
  sub << { pat: pat, styles: theme[:active] } if pat
@@ -834,15 +835,32 @@ module Squared
834
835
  end
835
836
 
836
837
  def append_repeat(flag, opts)
837
- opts.each { |val| @session << "--#{flag}=#{shell_escape(val, quote: true)}" }
838
+ opts.each { |val| @session << shell_option(flag, val, quote: true) }
839
+ end
840
+
841
+ def append_value(opts, delim: false, escape: true)
842
+ @session << '--' if delim && !opts.empty?
843
+ opts.each { |val| @session << (escape ? shell_escape(val) : shell_quote(val)) }
838
844
  end
839
845
 
840
- def append_value(opts)
841
- opts.each { |val| @session << shell_escape(val) }
846
+ def append_first(list, flag: true, equals: false, quote: false, **kwargs)
847
+ list.each do |opt|
848
+ next unless (val = option(opt, **kwargs))
849
+
850
+ return @session << (if flag
851
+ shell_option(opt, equals ? val : nil, quote: quote)
852
+ else
853
+ shell_quote(val)
854
+ end)
855
+ end
842
856
  end
843
857
 
844
- def append_option(list, **kwargs)
845
- list.each { |val| @session << "--#{val}" if option(val, **kwargs) }
858
+ def append_option(list, equals: false, quote: false, **kwargs)
859
+ list.each do |flag|
860
+ next unless (val = option(flag, **kwargs))
861
+
862
+ @session << shell_option(flag, equals ? val : nil, quote: quote)
863
+ end
846
864
  end
847
865
 
848
866
  def append_nocolor(flag = nil)
@@ -900,16 +918,17 @@ module Squared
900
918
  ret && !ret.empty? ? ret : [val]
901
919
  end
902
920
 
903
- def pwd_set(done = nil, &blk)
921
+ def pwd_set(done = nil, pass: false, &blk)
904
922
  pwd = Pathname.pwd
905
923
  if block_given?
906
- if path == pwd
907
- instance_eval(&blk)
924
+ if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join >= RUBY_VERSION)
925
+ ret = instance_eval(&blk)
908
926
  else
909
927
  Dir.chdir(path)
910
- instance_eval(&blk)
928
+ ret = instance_eval(&blk)
911
929
  Dir.chdir(pwd)
912
930
  end
931
+ ret
913
932
  elsif @pwd == pwd
914
933
  @pwd = nil
915
934
  pwd unless done
@@ -1003,7 +1022,7 @@ module Squared
1003
1022
  end
1004
1023
 
1005
1024
  def variables
1006
- VAR_SET
1025
+ Base.tasks + VAR_SET
1007
1026
  end
1008
1027
 
1009
1028
  def borderstyle
@@ -1013,7 +1032,7 @@ module Squared
1013
1032
  end
1014
1033
 
1015
1034
  def headerstyle
1016
- { pat: /^(\S+)(\s+)$/, styles: theme[:header] }
1035
+ { pat: /\A(\S+)(\s+)\z/, styles: theme[:header] }
1017
1036
  end
1018
1037
 
1019
1038
  def scriptargs
@@ -1021,7 +1040,7 @@ module Squared
1021
1040
  end
1022
1041
  end
1023
1042
 
1024
- Application.impl_project = Base
1043
+ Application.implement(Base, base: true)
1025
1044
  Application.attr_banner = Common::SymSet.new(%i[name project path ref group parent])
1026
1045
  end
1027
1046
  end