squared 0.4.14 → 0.4.16

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,9 +16,10 @@ 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
- pass exclude].freeze
22
+ pass only exclude].freeze
23
23
  BLK_SET = %i[run depend doc lint test copy clean].freeze
24
24
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
25
25
  URI_SCHEME = %r{^([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
@@ -73,8 +73,8 @@ module Squared
73
73
  'unpack' => %i[zip tar gem ext].freeze
74
74
  })
75
75
 
76
- attr_reader :name, :project, :workspace, :path, :theme, :exception, :pipe, :verbose,
77
- :group, :parent, :dependfile
76
+ attr_reader :name, :project, :workspace, :path, :theme, :group, :parent, :dependfile,
77
+ :exception, :pipe, :verbose
78
78
 
79
79
  def initialize(workspace, path, name, *, group: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON],
80
80
  **kwargs)
@@ -89,27 +89,24 @@ module Squared
89
89
  @test = kwargs[:test]
90
90
  @copy = kwargs[:copy]
91
91
  @clean = kwargs[:clean]
92
- @version = kwargs[:version]
93
92
  @release = kwargs[:release]
93
+ self.version = kwargs[:version]
94
+ self.exception = kwargs[:exception]
95
+ self.pipe = kwargs[:pipe]
96
+ self.verbose = kwargs[:verbose]
94
97
  @output = []
95
98
  @ref = []
96
99
  @children = []
97
- @events = {
98
- first: first,
99
- last: last,
100
- error: error
101
- }
100
+ @events = hashobj.update({ first: first, last: last, error: error })
102
101
  @envname = env_key(@name).freeze
103
102
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
104
103
  @parent = nil
105
104
  @global = false
106
105
  @index = -1
107
106
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
108
- exception_set kwargs[:exception]
109
- pipe_set kwargs[:pipe]
110
- verbose_set kwargs[:verbose]
111
107
  graph_set kwargs[:graph]
112
108
  pass_set kwargs[:pass]
109
+ only_set kwargs[:only]
113
110
  exclude_set kwargs[:exclude]
114
111
  archive_set kwargs[:archive]
115
112
  theme_set common
@@ -171,9 +168,7 @@ module Squared
171
168
  def initialize_events(ref, **)
172
169
  return unless (events = @workspace.events_get(group: @group, ref: ref))
173
170
 
174
- events.each do |task, data|
175
- data.each { |ev, blk| (@events[ev] ||= {})[task] ||= [blk] }
176
- end
171
+ events.each { |task, data| data.each { |ev, blk| @events[ev][task] ||= [blk] } }
177
172
  end
178
173
 
179
174
  def initialize_logger(log: nil, **)
@@ -238,8 +233,13 @@ module Squared
238
233
  end
239
234
  end
240
235
 
236
+ def ==(other)
237
+ equal?(other)
238
+ end
239
+
241
240
  def <=>(other)
242
- return 0 unless workspace == other.workspace
241
+ return unless workspace == other.workspace
242
+ return 0 if equal?(other)
243
243
 
244
244
  a, b = graph_deps
245
245
  return 1 if a.include?(other)
@@ -266,14 +266,35 @@ module Squared
266
266
  -1
267
267
  elsif f.any? { |val| e.include?(val) }
268
268
  1
269
- elsif @index != -1 && (i = other.instance_variable_get(:@index)) != -1
270
- @index < i ? -1 : 1
271
- else
272
- 0
269
+ elsif @index >= 0 && (i = other.instance_variable_get(:@index)) >= 0
270
+ @index <=> i
273
271
  end
274
272
  rescue StandardError => e
275
273
  log&.debug e
276
- 0
274
+ nil
275
+ end
276
+
277
+ def version=(val)
278
+ @version = val&.to_s
279
+ end
280
+
281
+ def exception=(val)
282
+ @exception = env_bool(val, workspace.exception, strict: true)
283
+ end
284
+
285
+ def pipe=(val)
286
+ @pipe = env_pipe(val, workspace.pipe, strict: true)
287
+ end
288
+
289
+ def verbose=(val)
290
+ @verbose = case val
291
+ when NilClass
292
+ workspace.verbose
293
+ when String
294
+ env_bool(val, workspace.verbose, strict: true, index: true)
295
+ else
296
+ val
297
+ end
277
298
  end
278
299
 
279
300
  def ref
@@ -286,7 +307,7 @@ module Squared
286
307
 
287
308
  namespace name do
288
309
  Base.subtasks do |action, flags|
289
- next if @pass.include?(action)
310
+ next if task_pass?(action)
290
311
 
291
312
  namespace action do
292
313
  flags.each do |flag|
@@ -501,11 +522,9 @@ module Squared
501
522
  if proj.respond_to?(meth.to_sym)
502
523
  begin
503
524
  proj.__send__(meth, sync: sync)
504
- rescue StandardError => e
505
- ret = on(:error, :prereqs, e)
506
- raise unless ret == true
507
- else
508
525
  next
526
+ rescue StandardError => e
527
+ on_error(:prereqs, e, exception: true)
509
528
  end
510
529
  end
511
530
  warn log_message(Logger::WARN, name, 'method not found', subject: 'prereqs', hint: meth)
@@ -558,9 +577,7 @@ module Squared
558
577
  begin
559
578
  @clean.each { |cmd, opts| build(cmd.to_s, opts, sync: sync) }
560
579
  rescue StandardError => e
561
- log&.error e
562
- ret = on(:error, from, e)
563
- raise if exception && ret != true
580
+ on_error e, from
564
581
  end
565
582
  else
566
583
  if @clean.is_a?(Enumerable) && !series?(@clean)
@@ -609,8 +626,7 @@ module Squared
609
626
  end
610
627
  ret = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
611
628
  rescue StandardError => e
612
- ret = on(:error, :graph, e)
613
- raise unless ret == true
629
+ on_error(:graph, e, exception: true)
614
630
  else
615
631
  if out
616
632
  [out, ret]
@@ -619,12 +635,13 @@ module Squared
619
635
  end
620
636
  end
621
637
 
622
- def unpack(target, uri:, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {}, from: :unpack)
638
+ def unpack(target, file = nil, uri: nil, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {},
639
+ verbose: self.verbose, from: :unpack)
623
640
  if !target.exist?
624
641
  target.mkpath
625
642
  elsif !target.directory?
626
643
  raise_error('invalid location', hint: target)
627
- elsif !target.empty?
644
+ elsif !file && !target.empty?
628
645
  raise_error('directory not empty', hint: target) unless force || env('UNPACK_FORCE')
629
646
  create = true
630
647
  end
@@ -651,44 +668,52 @@ module Squared
651
668
  if (val = env('HEADERS')) && (val = parse_json(val, hint: "HEADERS_#{@envname}"))
652
669
  headers = headers.is_a?(Hash) ? headers.merge(val) : val
653
670
  end
654
- data = nil
655
- (uri = Array(uri)).each_with_index do |url, index|
656
- fetch_uri(url, headers) do |f|
657
- data = f.read
658
- if algo && algo.hexdigest(data) != digest
659
- data = nil
660
- raise_error("checksum failed: #{digest}", hint: url) if index == uri.size - 1
661
- end
662
- next if ext && index == 0
663
-
664
- case f.content_type
665
- when 'application/zip'
666
- ext = 'zip'
667
- when 'application/x-gzip'
668
- ext = 'tgz'
669
- when 'application/x-xz'
670
- ext = 'txz'
671
+ if file
672
+ ext ||= File.extname(file)[1..-1]
673
+ else
674
+ data = nil
675
+ (uri = Array(uri)).each_with_index do |url, index|
676
+ fetch_uri(url, headers) do |f|
677
+ data = f.read
678
+ if algo && algo.hexdigest(data) != digest
679
+ data = nil
680
+ raise_error("checksum failed: #{digest}", hint: url) if index == uri.size - 1
681
+ end
682
+ next if ext && index == 0
683
+
684
+ case f.content_type
685
+ when 'application/zip'
686
+ ext = 'zip'
687
+ when 'application/x-gzip'
688
+ ext = 'tgz'
689
+ when 'application/x-xz'
690
+ ext = 'txz'
691
+ end
671
692
  end
693
+ break uri = url if data
694
+ end
695
+ unless data && (ext ||= URI.parse(uri).path[/\.(\w+)(?:\?|\z)/, 1])
696
+ raise_error("no content#{data ? ' type' : ''}", hint: uri)
672
697
  end
673
- break uri = url if data
674
- end
675
- unless data && (ext ||= URI.parse(uri).path[/\.(\w+)(?:\?|\z)/, 1])
676
- raise_error("no content#{data ? ' type' : ''}", hint: uri)
677
698
  end
678
699
  ext = ext.downcase
679
700
  if (val = env("#{%w[zip 7z gem].include?(ext) ? ext.upcase : 'TAR'}_DEPTH", ignore: false))
680
701
  depth = val.to_i
681
702
  end
682
703
  begin
683
- if ext == 'gem'
684
- dir = Dir.mktmpdir
685
- file = File.new(File.join(dir, File.basename(uri)), 'w')
686
- else
687
- require 'tempfile'
688
- file = Tempfile.new("#{name}-")
704
+ unless file
705
+ if ext == 'gem'
706
+ dir = Dir.mktmpdir
707
+ file = File.new(File.join(dir, File.basename(uri)), 'w')
708
+ else
709
+ require 'tempfile'
710
+ file = Tempfile.new("#{name}-")
711
+ end
712
+ file.write(data)
713
+ file.close
714
+ file = Pathname.new(file)
715
+ delete = true
689
716
  end
690
- file.write(data)
691
- file.close
692
717
  if create
693
718
  warn log_message(Logger::WARN, 'force remove', subject: name, hint: target)
694
719
  target.rmtree
@@ -696,7 +721,7 @@ module Squared
696
721
  end
697
722
  case ext
698
723
  when 'zip', 'aar'
699
- session 'unzip', shell_quote(file.path), quote_option('d', target)
724
+ session 'unzip', shell_quote(file), quote_option('d', target)
700
725
  when 'tar', 'tgz', 'tar.gz', 'tar.xz', 'gz', 'xz'
701
726
  flags = +(verbose ? 'v' : '')
702
727
  if ext.end_with?('gz')
@@ -704,24 +729,24 @@ module Squared
704
729
  elsif ext.end_with?('xz')
705
730
  flags += 'J'
706
731
  end
707
- session 'tar', "-x#{flags}", basic_option('strip-components', depth), quote_option('f', file.path),
732
+ session 'tar', "-x#{flags}", basic_option('strip-components', depth), quote_option('f', file),
708
733
  quote_option('C', target)
709
734
  depth = 0
710
735
  when '7z'
711
- session '7z', 'x', shell_quote(file.path), "-o#{shell_quote(target)}"
736
+ session '7z', 'x', shell_quote(file), "-o#{shell_quote(target)}"
712
737
  when 'gem'
713
- session 'gem', 'unpack', shell_quote(file.path), quote_option('target', target)
738
+ session 'gem', 'unpack', shell_quote(file), quote_option('target', target)
714
739
  depth = 0 unless val
715
740
  else
716
- raise_error("unsupported format: #{ext}", hint: uri)
741
+ raise_error("unsupported format: #{ext}", hint: uri || file)
717
742
  end
718
- run(sync: sync, from: from)
743
+ run(sync: sync, banner: verbose, from: from)
719
744
  while depth > 0 && target.children.size == 1
720
745
  entry = target.children.first
721
746
  break unless entry.directory?
722
747
 
723
748
  i = 0
724
- while (dest = target + "#{File.basename(file.path)}-#{i}").exist?
749
+ while (dest = target + "#{File.basename(file)}-#{i}").exist?
725
750
  i += 1
726
751
  end
727
752
  FileUtils.mv(entry, dest)
@@ -733,8 +758,8 @@ module Squared
733
758
  ensure
734
759
  if dir
735
760
  remove_entry dir
736
- else
737
- file&.unlink
761
+ elsif delete && file&.exist?
762
+ file.unlink
738
763
  end
739
764
  end
740
765
  end
@@ -752,7 +777,7 @@ module Squared
752
777
  end
753
778
 
754
779
  def event(name, key, *args, override: false, **kwargs, &blk)
755
- data = @events[name.to_sym] ||= {}
780
+ data = @events[name.to_sym]
756
781
  items = if override
757
782
  data[key.to_sym] = []
758
783
  else
@@ -799,6 +824,8 @@ module Squared
799
824
  graph_set val
800
825
  when :pass
801
826
  pass_set val
827
+ when :only
828
+ only_set val
802
829
  when :exclude
803
830
  exclude_set val
804
831
  when :parent
@@ -966,11 +993,11 @@ module Squared
966
993
  puts_oe(*args, pipe: pipe)
967
994
  end
968
995
 
969
- def run(cmd = @session, var = nil, exception: @exception, sync: true, from: nil, banner: true, chdir: path,
996
+ def run(cmd = @session, var = nil, exception: self.exception, sync: true, from: nil, banner: true, chdir: path,
970
997
  interactive: nil, **)
971
998
  unless cmd
972
- from = from&.to_s || 'unknown'
973
- return warn log_message(Logger::WARN, 'no command given', subject: project, hint: from, pass: true)
999
+ warn log_message(Logger::WARN, 'no command given', subject: project, hint: from || 'unknown', pass: true)
1000
+ return
974
1001
  end
975
1002
  i = interactive && !(@session && option('y'))
976
1003
  cmd = cmd.target if cmd.is_a?(OptionPartition)
@@ -985,7 +1012,7 @@ module Squared
985
1012
  ['Run', 'Y']
986
1013
  end
987
1014
  unless confirm("#{title}? [#{sub_style(cmd, styles: theme[:inline])}] #{y == 'Y' ? '[Y/n]' : '[y/N]'} ", y)
988
- raise_error('user cancelled', hint: from)
1015
+ exit 1
989
1016
  end
990
1017
  end
991
1018
  log&.info cmd
@@ -1010,10 +1037,7 @@ module Squared
1010
1037
  ret = shell(*args, chdir: chdir, exception: exception)
1011
1038
  end
1012
1039
  rescue StandardError => e
1013
- log&.error e
1014
- ret = on(:error, from, e)
1015
- raise unless ret == true
1016
-
1040
+ on_error(from, e, exception: true)
1017
1041
  false
1018
1042
  else
1019
1043
  on :last, from
@@ -1026,7 +1050,7 @@ module Squared
1026
1050
  begin
1027
1051
  cmd.flatten.each { |val| run(val, env, sync: sync, banner: banner, **kwargs) }
1028
1052
  rescue StandardError => e
1029
- ret = on(:error, from, e)
1053
+ ret = on :error, from, e
1030
1054
  raise unless ret == true
1031
1055
  end
1032
1056
  on :last, from
@@ -1145,7 +1169,7 @@ module Squared
1145
1169
  done
1146
1170
  end
1147
1171
 
1148
- def graph_collect(target, start = [], data: {}, pass: [])
1172
+ def graph_collect(target, start = [], data: {}, pass: [], root: [])
1149
1173
  deps = []
1150
1174
  (start.empty? ? target.instance_variable_get(:@graph) : start)&.each do |val|
1151
1175
  next if pass.include?(val)
@@ -1158,10 +1182,12 @@ module Squared
1158
1182
  items = workspace.find(group: val, ref: val.to_sym)
1159
1183
  end
1160
1184
  items.each do |proj|
1161
- next if pass.include?(proj.name)
1185
+ next if pass.include?(name = proj.name)
1162
1186
 
1163
- graph_collect(proj, data: data, pass: pass) if proj.graph? && !data.key?(proj.name)
1164
- next if (objs = data.fetch(proj.name, [])).include?(target)
1187
+ if proj.graph? && !data.key?(name) && !root.include?(name)
1188
+ graph_collect(proj, data: data, pass: pass, root: root + [name, target.name])
1189
+ end
1190
+ next if (objs = data.fetch(name, [])).include?(target)
1165
1191
 
1166
1192
  deps << proj
1167
1193
  deps.concat(objs)
@@ -1271,6 +1297,10 @@ module Squared
1271
1297
  puts 'Success'
1272
1298
  end
1273
1299
 
1300
+ def print_error(err, loglevel: Logger::WARN, pass: false)
1301
+ warn log_message(loglevel, err, pass: pass) if warning?
1302
+ end
1303
+
1274
1304
  def print_item(*val)
1275
1305
  puts unless printfirst?
1276
1306
  printsucc
@@ -1313,7 +1343,7 @@ module Squared
1313
1343
  ret.join("\n")
1314
1344
  end
1315
1345
 
1316
- def print_status(*args, from: nil)
1346
+ def print_status(*args, from: nil, **kwargs)
1317
1347
  return if stdin?
1318
1348
 
1319
1349
  case from
@@ -1322,6 +1352,12 @@ module Squared
1322
1352
  out[1] = sub_style(out[1], pat: /^( +major )(\d+)(.+)$/, styles: theme[:major], index: 2)
1323
1353
  out[1] = sub_style(out[1], pat: /^(.+)(minor )(\d+)(.+)$/, styles: theme[:active], index: 3)
1324
1354
  puts out
1355
+ when :completed
1356
+ if verbose && kwargs[:start]
1357
+ msg = sub_style('completed', styles: theme[:active])
1358
+ puts log_message(Logger::INFO, *args, msg, subject: kwargs[:subject],
1359
+ hint: time_format(epochtime - kwargs[:start]))
1360
+ end
1325
1361
  end
1326
1362
  end
1327
1363
 
@@ -1614,13 +1650,9 @@ module Squared
1614
1650
  multiple: false, force: true, **kwargs)
1615
1651
  puts if !series && !printfirst?
1616
1652
  msg = "#{msg} (optional)" unless force
1617
- unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) || !force
1618
- raise_error 'user cancelled'
1619
- end
1620
- if ret.nil? || ret.empty?
1621
- return unless force
1622
-
1623
- exit 1
1653
+ unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) && !ret.empty?
1654
+ exit 1 if force
1655
+ return
1624
1656
  end
1625
1657
  ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1626
1658
  if column
@@ -1666,8 +1698,8 @@ module Squared
1666
1698
  ret
1667
1699
  end
1668
1700
 
1669
- def command_args(args, force: false, **kwargs)
1670
- return unless args.size == 1 && !option('i', 'interactive', **kwargs, equals: '0')
1701
+ def command_args(args, min: 0, force: false, **kwargs)
1702
+ return if args.size > min || option('i', 'interactive', **kwargs, equals: '0')
1671
1703
 
1672
1704
  readline('Enter arguments', force: force)
1673
1705
  end
@@ -1729,6 +1761,21 @@ module Squared
1729
1761
  fill ? semver(ret) : ret
1730
1762
  end
1731
1763
 
1764
+ def semcmp(val, other)
1765
+ a, b = [val, other].map! { |ver| ver.match(SEM_VER) }
1766
+ return 1 unless a
1767
+ return -1 unless b
1768
+ return 0 if a[0] == b[0]
1769
+
1770
+ a, b = [a, b].map! { |c| [c[1], c[3], c[5] || '0'] }
1771
+ a.each_with_index do |c, index|
1772
+ next if c == (d = b[index])
1773
+
1774
+ return c.to_i < d.to_i ? 1 : -1
1775
+ end
1776
+ 0
1777
+ end
1778
+
1732
1779
  def indexitem(val)
1733
1780
  [$1.to_i, $2 && $2[1..-1]] if val =~ /\A[=^#{indexchar}](\d+)(:.+)?\z/
1734
1781
  end
@@ -1770,9 +1817,9 @@ module Squared
1770
1817
  end
1771
1818
 
1772
1819
  def on(event, from, *args, **kwargs)
1773
- return unless from && (data = @events[event])
1820
+ return unless from && @events.key?(event)
1774
1821
 
1775
- data[from]&.each do |obj|
1822
+ @events[event][from]&.each do |obj|
1776
1823
  target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
1777
1824
  [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
1778
1825
  else
@@ -1789,12 +1836,21 @@ module Squared
1789
1836
  end
1790
1837
  end
1791
1838
 
1839
+ def on_error(err, from, exception: self.exception, pass: false, dryrun: false)
1840
+ log&.error err
1841
+ unless dryrun
1842
+ ret = on :error, from, err
1843
+ raise err if exception && ret != true
1844
+ end
1845
+ print_error(err, pass: pass) unless ret
1846
+ end
1847
+
1792
1848
  def pwd_set(done = nil, pass: false, from: nil, dryrun: false)
1793
1849
  pwd = Pathname.pwd
1794
1850
  if block_given?
1795
1851
  begin
1796
1852
  pass = semscan(pass).join <= RUBY_VERSION if pass.is_a?(String)
1797
- if (path == pwd || pass == true) && !workspace.jruby_win?
1853
+ if (path == pwd || pass == true) && (workspace.mri? || !workspace.windows?)
1798
1854
  ret = yield
1799
1855
  else
1800
1856
  Dir.chdir(path)
@@ -1802,11 +1858,7 @@ module Squared
1802
1858
  Dir.chdir(pwd)
1803
1859
  end
1804
1860
  rescue StandardError => e
1805
- log&.error e
1806
- unless dryrun
1807
- ret = on(:error, from, e)
1808
- raise if exception && ret != true
1809
- end
1861
+ on_error(e, from, dryrun: dryrun)
1810
1862
  else
1811
1863
  ret
1812
1864
  end
@@ -1895,25 +1947,6 @@ module Squared
1895
1947
  @parent = val if val.is_a?(Project::Base)
1896
1948
  end
1897
1949
 
1898
- def exception_set(val)
1899
- @exception = env_bool(val, workspace.exception, strict: true)
1900
- end
1901
-
1902
- def pipe_set(val)
1903
- @pipe = env_pipe(val, workspace.pipe, strict: true)
1904
- end
1905
-
1906
- def verbose_set(val)
1907
- @verbose = case val
1908
- when NilClass
1909
- workspace.verbose
1910
- when String
1911
- env_bool(val, workspace.verbose, strict: true, index: true)
1912
- else
1913
- val
1914
- end
1915
- end
1916
-
1917
1950
  def graph_set(val)
1918
1951
  @graph = if val
1919
1952
  Array(val).map { |s| workspace.prefix ? workspace.task_name(s).to_sym : s.to_sym }.freeze
@@ -1921,7 +1954,11 @@ module Squared
1921
1954
  end
1922
1955
 
1923
1956
  def pass_set(val)
1924
- @pass = (val ? as_a(val, :to_s) : []).freeze
1957
+ @pass = Array(val).freeze
1958
+ end
1959
+
1960
+ def only_set(val)
1961
+ @only = val && as_a(val, :to_s).freeze
1925
1962
  end
1926
1963
 
1927
1964
  def exclude_set(val)
@@ -1980,6 +2017,10 @@ module Squared
1980
2017
  end
1981
2018
  end
1982
2019
 
2020
+ def task_pass?(key)
2021
+ @only ? !@only.include?(key) : @pass.include?(key)
2022
+ end
2023
+
1983
2024
  def projectpath?(val)
1984
2025
  val = Pathname.new(val).cleanpath
1985
2026
  val.absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?(File.join('..', ''))
@@ -2081,6 +2122,14 @@ module Squared
2081
2122
  BLK_SET
2082
2123
  end
2083
2124
 
2125
+ def hashobj
2126
+ Workspace::Support.hashobj
2127
+ end
2128
+
2129
+ def hashlist
2130
+ Workspace::Support.hashlist
2131
+ end
2132
+
2084
2133
  def borderstyle
2085
2134
  ((data = workspace.banner_get(*@ref, group: group)) && data[:border]) || theme[:border]
2086
2135
  end