squared 0.4.13 → 0.4.15

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.
@@ -9,7 +9,6 @@ module Squared
9
9
  module Workspace
10
10
  module Project
11
11
  class Base
12
- include Comparable
13
12
  include Common::Format
14
13
  include System
15
14
  include Shell
@@ -17,6 +16,7 @@ module Squared
17
16
  include Utils
18
17
  include Support
19
18
  include Rake::DSL
19
+ include ::Comparable
20
20
 
21
21
  VAR_SET = %i[parent global script index envname desc dependfile dependindex theme archive env dev prod graph
22
22
  pass exclude].freeze
@@ -76,8 +76,8 @@ module Squared
76
76
  attr_reader :name, :project, :workspace, :path, :theme, :exception, :pipe, :verbose,
77
77
  :group, :parent, :dependfile
78
78
 
79
- def initialize(workspace, path, name, *, group: nil, archive: nil, first: {}, last: {}, error: {},
80
- common: ARG[:COMMON], **kwargs)
79
+ def initialize(workspace, path, name, *, group: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON],
80
+ **kwargs)
81
81
  @path = path
82
82
  @workspace = workspace
83
83
  @name = name.to_s.freeze
@@ -90,14 +90,7 @@ module Squared
90
90
  @copy = kwargs[:copy]
91
91
  @clean = kwargs[:clean]
92
92
  @version = kwargs[:version]
93
- @archive = case archive
94
- when String, Array
95
- { uri: archive }
96
- when Hash
97
- archive
98
- end
99
93
  @release = kwargs[:release]
100
- @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
101
94
  @output = []
102
95
  @ref = []
103
96
  @children = []
@@ -106,6 +99,7 @@ module Squared
106
99
  last: last,
107
100
  error: error
108
101
  }
102
+ @envname = env_key(@name).freeze
109
103
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
110
104
  @parent = nil
111
105
  @global = false
@@ -114,10 +108,11 @@ module Squared
114
108
  exception_set kwargs[:exception]
115
109
  pipe_set kwargs[:pipe]
116
110
  verbose_set kwargs[:verbose]
117
- theme_set common
118
111
  graph_set kwargs[:graph]
119
112
  pass_set kwargs[:pass]
120
113
  exclude_set kwargs[:exclude]
114
+ archive_set kwargs[:archive]
115
+ theme_set common
121
116
  initialize_ref Base.ref
122
117
  end
123
118
 
@@ -205,7 +200,7 @@ module Squared
205
200
  file &&= @workspace.home.join(env('LOG_DIR', ''), file).realdirpath
206
201
  rescue StandardError => e
207
202
  file = nil
208
- warn log_message(Logger::WARN, e, pass: true) if warning?
203
+ warn log_message(Logger::WARN, e) if warning?
209
204
  end
210
205
  log[:progname] ||= @name
211
206
  if (val = env('LOG_LEVEL', ignore: false))
@@ -243,21 +238,26 @@ module Squared
243
238
  end
244
239
  end
245
240
 
241
+ def ==(other)
242
+ equal?(other)
243
+ end
244
+
246
245
  def <=>(other)
247
- return 0 unless workspace == other.workspace
246
+ return unless workspace == other.workspace
247
+ return 0 if equal?(other)
248
248
 
249
249
  a, b = graph_deps
250
250
  return 1 if a.include?(other)
251
251
 
252
252
  c, d = graph_deps other
253
- e = b.reject { |val| d.include?(val) }
254
- f = d.reject { |val| b.include?(val) }
253
+ e = b - d
254
+ f = d - b
255
255
  if parent == other.parent
256
256
  g = []
257
257
  h = []
258
258
  else
259
- g = a.reject { |val| c.include?(val) }
260
- h = c.reject { |val| a.include?(val) }
259
+ g = a - c
260
+ h = c - a
261
261
  end
262
262
  g << self
263
263
  h << other
@@ -271,14 +271,12 @@ module Squared
271
271
  -1
272
272
  elsif f.any? { |val| e.include?(val) }
273
273
  1
274
- elsif @index != -1 && (i = other.instance_variable_get(:@index)) != -1
275
- @index < i ? -1 : 1
276
- else
277
- 0
274
+ elsif @index >= 0 && (i = other.instance_variable_get(:@index)) >= 0
275
+ @index <=> i
278
276
  end
279
277
  rescue StandardError => e
280
278
  log&.debug e
281
- 0
279
+ nil
282
280
  end
283
281
 
284
282
  def ref
@@ -420,7 +418,7 @@ module Squared
420
418
 
421
419
  out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
422
420
  if !out
423
- warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name, pass: true)
421
+ warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name)
424
422
  elsif out.respond_to?(:build)
425
423
  out.build
426
424
  end
@@ -475,9 +473,9 @@ module Squared
475
473
  case opts
476
474
  when Hash
477
475
  opts = append_hash(opts, build: true)
478
- cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
476
+ cmd = Array(cmd).push(flags).concat(opts).compact.join(' ')
479
477
  when Enumerable
480
- cmd = as_a(cmd).concat(opts.to_a)
478
+ cmd = Array(cmd).concat(opts.to_a)
481
479
  cmd.map! { |val| "#{val} #{flags}" } if flags
482
480
  cmd = cmd.join(' && ')
483
481
  else
@@ -513,9 +511,7 @@ module Squared
513
511
  next
514
512
  end
515
513
  end
516
- if warning?
517
- warn log_message(Logger::WARN, name, 'method not found', subject: 'prereqs', hint: meth, pass: true)
518
- end
514
+ warn log_message(Logger::WARN, name, 'method not found', subject: 'prereqs', hint: meth)
519
515
  end
520
516
  elsif proj.build?
521
517
  proj.build(sync: sync)
@@ -659,7 +655,7 @@ module Squared
659
655
  headers = headers.is_a?(Hash) ? headers.merge(val) : val
660
656
  end
661
657
  data = nil
662
- (uri = as_a(uri)).each_with_index do |url, index|
658
+ (uri = Array(uri)).each_with_index do |url, index|
663
659
  fetch_uri(url, headers) do |f|
664
660
  data = f.read
665
661
  if algo && algo.hexdigest(data) != digest
@@ -697,7 +693,7 @@ module Squared
697
693
  file.write(data)
698
694
  file.close
699
695
  if create
700
- warn log_message(Logger::WARN, 'force remove', subject: name, hint: target, pass: true)
696
+ warn log_message(Logger::WARN, 'force remove', subject: name, hint: target)
701
697
  target.rmtree
702
698
  target.mkpath
703
699
  end
@@ -810,6 +806,8 @@ module Squared
810
806
  exclude_set val
811
807
  when :parent
812
808
  parent_set val
809
+ when :archive
810
+ archive_set val
813
811
  when :run
814
812
  run_set(*args, **kwargs)
815
813
  when :script
@@ -971,16 +969,28 @@ module Squared
971
969
  puts_oe(*args, pipe: pipe)
972
970
  end
973
971
 
974
- def run(cmd = @session, var = nil, exception: @exception, sync: true, from: nil, banner: true, chdir: path, **)
972
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, from: nil, banner: true, chdir: path,
973
+ interactive: nil, **)
975
974
  unless cmd
976
- if warning?
977
- from &&= from.to_s
978
- warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given', pass: true)
979
- end
980
- return
975
+ from = from&.to_s || 'unknown'
976
+ return warn log_message(Logger::WARN, 'no command given', subject: project, hint: from, pass: true)
981
977
  end
978
+ i = interactive && !(@session && option('y'))
982
979
  cmd = cmd.target if cmd.is_a?(OptionPartition)
983
980
  cmd = session_done cmd
981
+ if i
982
+ title, y = case interactive
983
+ when Array
984
+ interactive
985
+ when String
986
+ [interactive, 'N']
987
+ else
988
+ ['Run', 'Y']
989
+ end
990
+ unless confirm("#{title}? [#{sub_style(cmd, styles: theme[:inline])}] #{y == 'Y' ? '[Y/n]' : '[y/N]'} ", y)
991
+ raise_error('user cancelled', hint: from)
992
+ end
993
+ end
984
994
  log&.info cmd
985
995
  on :first, from
986
996
  begin
@@ -1041,7 +1051,7 @@ module Squared
1041
1051
  elsif obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
1042
1052
  build(*obj, **kwargs)
1043
1053
  elsif obj
1044
- run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, **kwargs)
1054
+ run_s(*Array(obj), **kwargs)
1045
1055
  end
1046
1056
  end
1047
1057
  end
@@ -1088,7 +1098,7 @@ module Squared
1088
1098
 
1089
1099
  t = dedupe.call(proj.name)
1090
1100
  j = if out
1091
- if i == items.size - 1 || check.call(post = items[i + 1..-1]).empty?
1101
+ if i == items.size - 1 || check.call(post = items[(i + 1)..-1]).empty?
1092
1102
  true
1093
1103
  elsif !t.empty? && depth > 0
1094
1104
  post.reject { |pr| t.include?(pr) }.empty?
@@ -1100,9 +1110,9 @@ module Squared
1100
1110
  end
1101
1111
  if !out
1102
1112
  if !tasks && (script = workspace.script_get(:graph, group: proj.group, ref: proj.allref))
1103
- group = script[:graph]
1113
+ tasks = script[:graph]
1104
1114
  end
1105
- (tasks || group || (dev? ? ['build', 'copy'] : ['depend', 'build'])).each do |meth|
1115
+ (tasks || (dev? ? ['build', 'copy'] : ['depend', 'build'])).each do |meth|
1106
1116
  next if pass.include?(meth)
1107
1117
 
1108
1118
  if workspace.task_defined?(cmd = task_join(proj.name, meth))
@@ -1113,7 +1123,7 @@ module Squared
1113
1123
  end
1114
1124
  run(cmd, sync: false, banner: false)
1115
1125
  ENV.delete(key) if key
1116
- elsif proj.has?(meth, tasks || group ? nil : workspace.baseref)
1126
+ elsif proj.has?(meth, tasks ? nil : workspace.baseref)
1117
1127
  proj.__send__(meth.to_sym, sync: sync)
1118
1128
  end
1119
1129
  end
@@ -1138,7 +1148,7 @@ module Squared
1138
1148
  done
1139
1149
  end
1140
1150
 
1141
- def graph_collect(target, start = [], data: {}, pass: [])
1151
+ def graph_collect(target, start = [], data: {}, pass: [], root: [])
1142
1152
  deps = []
1143
1153
  (start.empty? ? target.instance_variable_get(:@graph) : start)&.each do |val|
1144
1154
  next if pass.include?(val)
@@ -1150,12 +1160,13 @@ module Squared
1150
1160
  else
1151
1161
  items = workspace.find(group: val, ref: val.to_sym)
1152
1162
  end
1153
-
1154
1163
  items.each do |proj|
1155
- next if pass.include?(proj.name)
1164
+ next if pass.include?(name = proj.name)
1156
1165
 
1157
- graph_collect(proj, data: data, pass: pass) if proj.graph? && !data.key?(proj.name)
1158
- next if (objs = data.fetch(proj.name, [])).include?(target)
1166
+ if proj.graph? && !data.key?(name) && !root.include?(name)
1167
+ graph_collect(proj, data: data, pass: pass, root: root + [name, target.name])
1168
+ end
1169
+ next if (objs = data.fetch(name, [])).include?(target)
1159
1170
 
1160
1171
  deps << proj
1161
1172
  deps.concat(objs)
@@ -1195,7 +1206,7 @@ module Squared
1195
1206
  end
1196
1207
  return ret == equals.to_s unless equals.nil?
1197
1208
 
1198
- ret.empty? || (ignore && as_a(ignore).any? { |val| ret == val.to_s }) ? default : ret
1209
+ ret.empty? || (ignore && Array(ignore).any? { |val| ret == val.to_s }) ? default : ret
1199
1210
  end
1200
1211
 
1201
1212
  def session(*cmd, prefix: cmd.first, main: true, path: true, options: true)
@@ -1243,7 +1254,7 @@ module Squared
1243
1254
  def option(*args, target: @session, prefix: target&.first, **kwargs)
1244
1255
  if prefix
1245
1256
  args.each do |val|
1246
- ret = env("#{stripext(prefix)}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
1257
+ ret = env(env_key(stripext(prefix), val), **kwargs)
1247
1258
  return ret if ret
1248
1259
  end
1249
1260
  end
@@ -1307,6 +1318,18 @@ module Squared
1307
1318
  ret.join("\n")
1308
1319
  end
1309
1320
 
1321
+ def print_status(*args, from: nil)
1322
+ return if stdin?
1323
+
1324
+ case from
1325
+ when :outdated
1326
+ out = print_footer("major #{args[0]} / minor #{args[1]} / patch #{args[2]}", right: true).split("\n")
1327
+ out[1] = sub_style(out[1], pat: /^( +major )(\d+)(.+)$/, styles: theme[:major], index: 2)
1328
+ out[1] = sub_style(out[1], pat: /^(.+)(minor )(\d+)(.+)$/, styles: theme[:active], index: 3)
1329
+ puts out
1330
+ end
1331
+ end
1332
+
1310
1333
  def format_desc(action, flag, opts = nil, **kwargs)
1311
1334
  return unless TASK_METADATA
1312
1335
 
@@ -1380,7 +1403,7 @@ module Squared
1380
1403
  out << ''
1381
1404
  end
1382
1405
  if from
1383
- out << from
1406
+ out << (from = from.to_s)
1384
1407
  pat = /\A(#{Regexp.escape(from)})(.*)\z/
1385
1408
  end
1386
1409
  else
@@ -1549,7 +1572,7 @@ module Squared
1549
1572
  raise_error("invalid JSON #{kind.name}", val, hint: hint) if kind && !ret.is_a?(kind)
1550
1573
  rescue StandardError => e
1551
1574
  log&.warn e
1552
- warn log_message(Logger::WARN, e, subject: name, pass: true) if warning?
1575
+ warn log_message(Logger::WARN, e, subject: name) if warning?
1553
1576
  else
1554
1577
  ret
1555
1578
  end
@@ -1606,25 +1629,28 @@ module Squared
1606
1629
  end
1607
1630
  ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1608
1631
  if column
1609
- a, b = as_a column
1610
- ret = as_a(ret).map! { |val| val[a, b || 1] }
1632
+ a, b = Array(column)
1633
+ ret = Array(ret).map! { |val| val[a, b || 1] }
1611
1634
  ret = ret.first unless multiple
1612
1635
  end
1613
1636
  if accept
1614
- a = as_a(ret).map { |val| sub_style(val, styles: theme[:inline]) }.join(', ')
1615
- accept = as_a(accept).map { |val| as_a(val) }
1637
+ hint = Array(ret).map { |val| sub_style(val, styles: theme[:inline]) }.join(', ')
1638
+ accept = Array(accept).map { |val| Array(val) }
1616
1639
  if accept.any? { |val| val[1] == true }
1617
1640
  ret = [ret]
1618
1641
  multiple = -1
1619
1642
  end
1620
1643
  loop do
1621
- c = confirm("#{accept.first[0]}#{a ? " [#{a}]" : ''} [y/N] ", 'N', timeout: 60)
1622
- if accept.shift[1] == true
1644
+ item = accept.first
1645
+ d, e = item[2] ? ['Y', '[Y/n]'] : ['N', '[y/N]']
1646
+ c = confirm("#{item[0]}#{a ? " [#{a}]" : ''} #{e} ", d, timeout: 60)
1647
+ if item[1] == true
1623
1648
  ret << c
1624
1649
  elsif !c
1625
1650
  break
1626
1651
  end
1627
- a = nil
1652
+ hint = nil
1653
+ accept.shift
1628
1654
  break if accept.empty?
1629
1655
  end
1630
1656
  exit 1 unless accept.empty?
@@ -1655,7 +1681,7 @@ module Squared
1655
1681
  if (ret = instance_eval(&blk)).nil?
1656
1682
  val
1657
1683
  else
1658
- ret.is_a?(Array) ? ret : [ret]
1684
+ Array(ret)
1659
1685
  end
1660
1686
  end
1661
1687
 
@@ -1709,13 +1735,17 @@ module Squared
1709
1735
  end
1710
1736
 
1711
1737
  def indexitem(val)
1712
- [$1.to_i, $2 && $2[1..-1]] if val =~ /\A\^(\d+)(:.+)?\z/
1738
+ [$1.to_i, $2 && $2[1..-1]] if val =~ /\A[=^#{indexchar}](\d+)(:.+)?\z/
1713
1739
  end
1714
1740
 
1715
1741
  def indexerror(val, list = nil)
1716
1742
  raise_error("requested index #{val}", hint: list && "of #{list.size}")
1717
1743
  end
1718
1744
 
1745
+ def indexchar
1746
+ workspace.windows? ? '=' : '^'
1747
+ end
1748
+
1719
1749
  def printsucc
1720
1750
  @@print_order += 1
1721
1751
  end
@@ -1768,7 +1798,8 @@ module Squared
1768
1798
  pwd = Pathname.pwd
1769
1799
  if block_given?
1770
1800
  begin
1771
- if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join <= RUBY_VERSION)
1801
+ pass = semscan(pass).join <= RUBY_VERSION if pass.is_a?(String)
1802
+ if (path == pwd || pass == true) && !workspace.jruby_win?
1772
1803
  ret = yield
1773
1804
  else
1774
1805
  Dir.chdir(path)
@@ -1888,19 +1919,9 @@ module Squared
1888
1919
  end
1889
1920
  end
1890
1921
 
1891
- def theme_set(common)
1892
- @theme = if !verbose
1893
- {}
1894
- elsif common
1895
- workspace.theme
1896
- else
1897
- __get__(:theme)[:project][to_sym] ||= {}
1898
- end
1899
- end
1900
-
1901
1922
  def graph_set(val)
1902
1923
  @graph = if val
1903
- as_a(val).map { |s| workspace.prefix ? workspace.task_name(s).to_sym : s.to_sym }.freeze
1924
+ Array(val).map { |s| workspace.prefix ? workspace.task_name(s).to_sym : s.to_sym }.freeze
1904
1925
  end
1905
1926
  end
1906
1927
 
@@ -1912,6 +1933,25 @@ module Squared
1912
1933
  @exclude = (val ? as_a(val, :to_sym) : []).freeze
1913
1934
  end
1914
1935
 
1936
+ def archive_set(val)
1937
+ @archive = case val
1938
+ when String, Array
1939
+ { uri: val }
1940
+ when Hash
1941
+ val
1942
+ end
1943
+ end
1944
+
1945
+ def theme_set(common)
1946
+ @theme = if !verbose
1947
+ {}
1948
+ elsif common
1949
+ workspace.theme
1950
+ else
1951
+ __get__(:theme)[:project][to_sym] ||= {}
1952
+ end
1953
+ end
1954
+
1915
1955
  def dependfile_set(list)
1916
1956
  @dependindex = list.index { |file| basepath(file).exist? }.tap do |index|
1917
1957
  @dependfile = @path + list[index || 0]
@@ -2011,6 +2051,10 @@ module Squared
2011
2051
  ARG[:BANNER] && !env('BANNER', equals: '0')
2012
2052
  end
2013
2053
 
2054
+ def pwd?
2055
+ path == Pathname.pwd
2056
+ end
2057
+
2014
2058
  def stdin?
2015
2059
  pipe == 0
2016
2060
  end
@@ -21,7 +21,8 @@ module Squared
21
21
  compose: {
22
22
  common: %w[all-resources compatibility dry-run ansi|b env-file=p f|file=p parallel=b profile=b progress=b
23
23
  project-directory=p p|project-name=e].freeze,
24
- build: %w[no-cache pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b ssh=qq].freeze,
24
+ build: %w[check no-cache pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b
25
+ ssh=qq].freeze,
25
26
  exec: %w[dry-run privileged d|detach e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
26
27
  run: %w[build dry-run no-deps quiet-pull remove-orphans rm P|service-ports use-aliases cap-add=b cap-drop=b
27
28
  d|detach entrypoint=q e|env=qq i|interactive=b? l|label=q name=b T|no-TTY=b? p|publish=e pull=b
@@ -159,7 +160,7 @@ module Squared
159
160
  compose! flag, args.to_a
160
161
  end
161
162
  when :exec, :run
162
- format_desc action, flag, "service,command#{flag == :exec ? '' : '?'},args*,opts*"
163
+ format_desc action, flag, "service,command#{flag == :exec ? '' : '?'}|:,args*,opts*"
163
164
  task flag, [:service] do |_, args|
164
165
  service = param_guard(action, flag, args: args, key: :service)
165
166
  compose!(flag, args.extras, service: service)
@@ -264,7 +265,6 @@ module Squared
264
265
  when Enumerable
265
266
  ret.merge(opts.to_a)
266
267
  end
267
-
268
268
  [args, flags].each_with_index do |target, index|
269
269
  if target.is_a?(String)
270
270
  ret << target
@@ -279,12 +279,12 @@ module Squared
279
279
  ret << quote_option('secret', @secrets, double: true)
280
280
  when Hash
281
281
  append = lambda do |type|
282
- as_a(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
282
+ Array(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
283
283
  end
284
284
  append.call(:file)
285
285
  append.call(:env)
286
286
  else
287
- as_a(@secrets).each { |arg| ret << quote_option('secret', arg) }
287
+ Array(@secrets).each { |arg| ret << quote_option('secret', arg) }
288
288
  end
289
289
  if (val = option('tag', ignore: false))
290
290
  append_tag val
@@ -312,7 +312,7 @@ module Squared
312
312
  when :bake
313
313
  unless op.empty?
314
314
  args = op.dup
315
- op.extras.clear
315
+ op.reset
316
316
  if Dir.exist?(args.last)
317
317
  if projectpath?(val = args.pop)
318
318
  context = val
@@ -359,7 +359,7 @@ module Squared
359
359
  both = run[:bind] + run[:tmpfs]
360
360
  diff = run[:bind].reject { |val| run[:tmpfs].include?(val) }
361
361
  delim = Regexp.new(",\\s*(?=#{both.join('|')})")
362
- as_a(@mounts).each do |val|
362
+ Array(@mounts).each do |val|
363
363
  args = []
364
364
  tmpfs = true
365
365
  val.split(delim).each do |opt|
@@ -565,7 +565,9 @@ module Squared
565
565
  end
566
566
 
567
567
  def append_command(flag, val, list, target: @session, from: nil)
568
- if (args = env('DOCKER_ARGS'))
568
+ if list.delete(':')
569
+ list << readline('Enter command [args]', force: true)
570
+ elsif (args = env('DOCKER_ARGS'))
569
571
  list << args
570
572
  end
571
573
  case flag
@@ -588,7 +590,7 @@ module Squared
588
590
  def append_file(type, target: @session)
589
591
  return unless type == 2 || type == 4 || @file.is_a?(Array)
590
592
 
591
- files = as_a(@file).map { |val| quote_option('file', path + val) }
593
+ files = Array(@file).map { |val| quote_option('file', path + val) }
592
594
  if target.is_a?(Set)
593
595
  target.merge(files)
594
596
  else
@@ -662,7 +664,7 @@ module Squared
662
664
  cols.each do |key|
663
665
  next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
664
666
 
665
- puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
667
+ puts "#{g + f} #{key}: #{Array(data[key]).join(', ')}" unless data[key].to_s.empty?
666
668
  end
667
669
  w = 9 + flag.to_s.size + 4 + ee.size
668
670
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])