squared 0.4.13 → 0.5.0

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.
@@ -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,18 +99,25 @@ module Squared
106
99
  last: last,
107
100
  error: error
108
101
  }
102
+ @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
109
103
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
110
104
  @parent = nil
111
105
  @global = false
106
+ @log = nil
107
+ @dev = nil
108
+ @prod = nil
109
+ @withargs = nil
110
+ @session = nil
112
111
  @index = -1
113
112
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
114
113
  exception_set kwargs[:exception]
115
114
  pipe_set kwargs[:pipe]
116
115
  verbose_set kwargs[:verbose]
117
- theme_set common
118
116
  graph_set kwargs[:graph]
119
117
  pass_set kwargs[:pass]
120
118
  exclude_set kwargs[:exclude]
119
+ archive_set kwargs[:archive]
120
+ theme_set common
121
121
  initialize_ref Base.ref
122
122
  end
123
123
 
@@ -271,8 +271,8 @@ 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
274
+ elsif @index >= 0 && (i = other.instance_variable_get(:@index)) >= 0
275
+ @index <= i ? -1 : 1
276
276
  else
277
277
  0
278
278
  end
@@ -301,31 +301,31 @@ module Squared
301
301
 
302
302
  format_desc action, flag, '(-)project*'
303
303
  task flag do |_, args|
304
- args = args.to_a.reject { |val| name == val.to_s }
304
+ args = args.to_a.reject { |val| name == val }
305
305
  if flag == :run
306
306
  graph args
307
307
  else
308
308
  out, done = graph(args, out: [])
309
309
  out.map! do |val|
310
310
  done.each_with_index do |proj, i|
311
- next unless val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/)
312
-
313
- val += " (#{i.succ})"
314
- break
311
+ if val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/)
312
+ val += " (#{i.succ})"
313
+ break
314
+ end
315
315
  end
316
316
  val
317
317
  end
318
318
  emphasize(out, title: path, right: true, border: borderstyle, sub: [
319
319
  { pat: /\A(#{Regexp.escape(path.to_s)})(.*)\z/, styles: theme[:header] },
320
320
  { pat: /\A(#{Regexp.escape(name)})(.*)\z/, styles: theme[:active] },
321
- { pat: /\A(.+ )(\()(\d+)(\))(.*)\z/, styles: theme[:inline], index: 3 }
321
+ { pat: /\A((?~ \() \()(\d+)(\).*)\z/, styles: theme[:inline], index: 2 }
322
322
  ])
323
323
  end
324
324
  end
325
325
  when 'unpack'
326
326
  format_desc(action, flag, 'tag/url,dir,digest?,f|force?', before: flag == :ext ? 'ext' : nil)
327
327
  params = %i[tag dir digest force]
328
- params.unshift(:ext) if flag == :ext
328
+ params.prepend(:ext) if flag == :ext
329
329
  task flag, params do |_, args|
330
330
  ext = flag == :ext ? param_guard(action, flag, args: args, key: :ext) : flag.to_s
331
331
  tag = param_guard(action, flag, args: args, key: :tag)
@@ -380,17 +380,8 @@ module Squared
380
380
  return self
381
381
  elsif !projectpath?(path = basepath(path)) || !checkdir?(path)
382
382
  return self
383
- else
384
- name = case name
385
- when String, Symbol
386
- name.to_s
387
- end
388
- end
389
- if @withargs
390
- data = @withargs.dup
391
- data.merge!(kwargs)
392
- kwargs = data
393
383
  end
384
+ kwargs = @withargs.yield_self { |data| data.dup.update(kwargs) } if @withargs
394
385
  kwargs[:group] = group unless kwargs.key?(:group)
395
386
  kwargs[:ref] = ref unless kwargs.key?(:ref)
396
387
  parent = self
@@ -462,7 +453,7 @@ module Squared
462
453
 
463
454
  cmd << a if (a = compose(as_get(b), d, script: true, args: e, from: from))
464
455
  end
465
- var.merge!(c) if c.is_a?(Hash)
456
+ var.update(c) if c.is_a?(Hash)
466
457
  end
467
458
  cmd = cmd.join(' && ')
468
459
  else
@@ -475,7 +466,7 @@ module Squared
475
466
  case opts
476
467
  when Hash
477
468
  opts = append_hash(opts, build: true)
478
- cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
469
+ cmd = as_a(cmd).append(flags).concat(opts).compact.join(' ')
479
470
  when Enumerable
480
471
  cmd = as_a(cmd).concat(opts.to_a)
481
472
  cmd.map! { |val| "#{val} #{flags}" } if flags
@@ -627,6 +618,7 @@ module Squared
627
618
  end
628
619
 
629
620
  def unpack(target, uri:, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {}, from: :unpack)
621
+ require 'open-uri'
630
622
  if !target.exist?
631
623
  target.mkpath
632
624
  elsif !target.directory?
@@ -660,7 +652,7 @@ module Squared
660
652
  end
661
653
  data = nil
662
654
  (uri = as_a(uri)).each_with_index do |url, index|
663
- fetch_uri(url, headers) do |f|
655
+ URI.open(url, headers) do |f|
664
656
  data = f.read
665
657
  if algo && algo.hexdigest(data) != digest
666
658
  data = nil
@@ -732,7 +724,7 @@ module Squared
732
724
  i += 1
733
725
  end
734
726
  FileUtils.mv(entry, dest)
735
- dest.children.each { |child| FileUtils.mv(child, target) }
727
+ dest.each_child { |child| FileUtils.mv(child, target) }
736
728
  dest.rmdir
737
729
  target = entry
738
730
  depth -= 1
@@ -790,6 +782,13 @@ module Squared
790
782
  end
791
783
 
792
784
  def variable_set(key, *args, **kwargs, &blk)
785
+ if block_given?
786
+ if blocks.include?(key)
787
+ series key, &blk
788
+ return self
789
+ end
790
+ args = block_args args, &blk
791
+ end
793
792
  if variables.include?(key) || blocks.include?(key)
794
793
  val = case args.size
795
794
  when 0
@@ -819,14 +818,7 @@ module Squared
819
818
  when :dependfile
820
819
  @dependfile = basepath(*args)
821
820
  else
822
- if block_given?
823
- if blocks.include?(key)
824
- series key, &blk
825
- return self
826
- end
827
- val = block_args val, &blk
828
- end
829
- instance_variable_set(:"@#{key}", val.first)
821
+ instance_variable_set(:"@#{key}", val)
830
822
  end
831
823
  else
832
824
  log&.warn "variable_set: @#{key} (private)"
@@ -984,7 +976,7 @@ module Squared
984
976
  log&.info cmd
985
977
  on :first, from
986
978
  begin
987
- if cmd.match?(/\A[^:]+:[^:]/) && workspace.task_defined?(cmd)
979
+ if cmd.start_with?(/[^:]+:[^:]/) && workspace.task_defined?(cmd)
988
980
  log&.warn "ENV discarded: #{var}" if var
989
981
  task_invoke(cmd, exception: exception, warning: warning?)
990
982
  else
@@ -996,7 +988,7 @@ module Squared
996
988
  when Enumerable
997
989
  cmd = command(*pre.to_a, cmd)
998
990
  else
999
- cmd = command(pre, cmd)
991
+ cmd = command pre, cmd
1000
992
  end
1001
993
  end
1002
994
  args = var.is_a?(Hash) ? [var, cmd] : [cmd]
@@ -1243,18 +1235,15 @@ module Squared
1243
1235
  def option(*args, target: @session, prefix: target&.first, **kwargs)
1244
1236
  if prefix
1245
1237
  args.each do |val|
1246
- ret = env("#{stripext(prefix)}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
1247
- return ret if ret
1238
+ next unless (ret = env("#{stripext(prefix)}_#{val.gsub(/\W/, '_')}".upcase, **kwargs))
1239
+ return ret unless block_given?
1240
+
1241
+ return yield ret
1248
1242
  end
1249
1243
  end
1250
1244
  nil
1251
1245
  end
1252
1246
 
1253
- def option_sanitize(opts, list, target: @session, **kwargs)
1254
- op = OptionPartition.new(opts, list, target, project: self, **kwargs)
1255
- [op.extras, op.values]
1256
- end
1257
-
1258
1247
  def option_clear(opts, target: @session, **kwargs)
1259
1248
  return unless target
1260
1249
 
@@ -1280,7 +1269,7 @@ module Squared
1280
1269
  styles = [:bold] + styles
1281
1270
  end
1282
1271
  end
1283
- n = Project.max_width(lines)
1272
+ n = line_width lines
1284
1273
  ch = ' ' * pad
1285
1274
  index = -1
1286
1275
  lines.map! do |val|
@@ -1296,7 +1285,7 @@ module Squared
1296
1285
  end
1297
1286
 
1298
1287
  def print_footer(*lines, sub: nil, reverse: false, right: false, **kwargs)
1299
- n = Project.max_width(lines)
1288
+ n = line_width lines
1300
1289
  lines.map! do |val|
1301
1290
  s = right ? val.rjust(n) : val.ljust(n)
1302
1291
  sub&.each { |h| s = sub_style(s, **h) }
@@ -1310,31 +1299,30 @@ module Squared
1310
1299
  def format_desc(action, flag, opts = nil, **kwargs)
1311
1300
  return unless TASK_METADATA
1312
1301
 
1313
- ret = [@desc, action]
1314
- ret << flag if flag
1315
- workspace.format_desc(ret, opts, **kwargs)
1302
+ workspace.format_desc([@desc, action, flag].compact, opts, **kwargs)
1316
1303
  end
1317
1304
 
1318
1305
  def format_banner(cmd, banner: true)
1319
1306
  return unless banner && banner?
1320
1307
 
1321
1308
  if (data = workspace.banner_get(*@ref, group: group))
1322
- return if data.empty?
1309
+ return if !data.command && data.order.empty?
1323
1310
 
1324
1311
  client = true
1325
1312
  else
1326
- data = { command: true, order: [:path], styles: theme[:banner], border: theme[:border] }
1313
+ data = Workspace::Support::BannerData.new(true, [:path], theme[:banner], theme[:border])
1327
1314
  end
1328
1315
  if verbose
1329
1316
  out = []
1330
- if data[:command]
1317
+ if data.command
1331
1318
  if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+))( |\z)/
1332
1319
  path = $3 || $2 || $1
1333
- cmd = cmd.sub(path, stripext(path).upcase)
1320
+ name = stripext path
1321
+ cmd = cmd.sub(path, data.command == 0 ? name : name.upcase)
1334
1322
  end
1335
1323
  out << cmd
1336
1324
  end
1337
- data[:order].each do |val|
1325
+ data.order.each do |val|
1338
1326
  if val.is_a?(Array)
1339
1327
  s = ' '
1340
1328
  found = false
@@ -1344,7 +1332,7 @@ module Squared
1344
1332
  meth
1345
1333
  elsif respond_to?(meth)
1346
1334
  found = true
1347
- __send__(meth)
1335
+ __send__ meth
1348
1336
  end
1349
1337
  end
1350
1338
  val = val.compact.join(s)
@@ -1354,9 +1342,9 @@ module Squared
1354
1342
  end
1355
1343
  out << val.to_s
1356
1344
  end
1357
- print_banner(*out, styles: data[:styles], border: data[:border], client: client)
1345
+ print_banner(*out, styles: data.styles, border: data.border, client: client)
1358
1346
  elsif workspace.series.multiple?
1359
- "## #{__send__(data[:order].first || :path)} ##"
1347
+ "## #{__send__(data.order.first || :path)} ##"
1360
1348
  end
1361
1349
  end
1362
1350
 
@@ -1433,14 +1421,15 @@ module Squared
1433
1421
  end
1434
1422
 
1435
1423
  def append_any(val, target: @session, build: false, delim: false)
1424
+ return unless val
1425
+
1436
1426
  if delim && !target.include?('--')
1437
1427
  target << '--'
1438
1428
  else
1439
1429
  delim = false
1440
1430
  end
1431
+ val = shell_split(val) if val.is_a?(String)
1441
1432
  case val
1442
- when String
1443
- target << val
1444
1433
  when Hash
1445
1434
  append_hash(val, target: target, build: build)
1446
1435
  when Enumerable
@@ -1471,7 +1460,7 @@ module Squared
1471
1460
  return target << (if flag
1472
1461
  shell_option(opt, equals ? val : nil, quote: quote, escape: escape, force: force)
1473
1462
  else
1474
- shell_quote(val)
1463
+ shell_quote val
1475
1464
  end)
1476
1465
  end
1477
1466
  nil
@@ -1481,17 +1470,18 @@ module Squared
1481
1470
  **kwargs)
1482
1471
  return if list.empty?
1483
1472
 
1484
- ret = []
1485
- list.flatten.each do |flag|
1486
- next unless (val = option(flag, target: target, **kwargs))
1473
+ [].tap do |ret|
1474
+ list.flatten.each do |flag|
1475
+ next unless (val = option(flag, target: target, **kwargs))
1487
1476
 
1488
- if val == '0' && no
1489
- flag = "no-#{flag}"
1490
- val = nil
1477
+ if val == '0' && no
1478
+ flag = "no-#{flag}"
1479
+ val = nil
1480
+ end
1481
+ ret << shell_option(flag, equals ? val : nil, escape: escape, quote: quote, force: force)
1491
1482
  end
1492
- ret << shell_option(flag, equals ? val : nil, escape: escape, quote: quote, force: force)
1483
+ ret.each { |val| target << val } unless ret.empty?
1493
1484
  end
1494
- ret.each { |val| target << val } unless ret.empty?
1495
1485
  end
1496
1486
 
1497
1487
  def append_nocolor(target: @session)
@@ -1539,9 +1529,9 @@ module Squared
1539
1529
  end
1540
1530
 
1541
1531
  def collect_hash(data, pass: [])
1542
- ret = []
1543
- data.each { |key, val| ret.concat(val) unless pass.include?(key) }
1544
- ret
1532
+ [].tap do |ret|
1533
+ data.each { |key, val| ret.concat(val) unless pass.include?(key) }
1534
+ end
1545
1535
  end
1546
1536
 
1547
1537
  def parse_json(val, kind: Hash, hint: nil)
@@ -1554,15 +1544,6 @@ module Squared
1554
1544
  ret
1555
1545
  end
1556
1546
 
1557
- def fetch_uri(*args, **kwargs, &blk)
1558
- require 'open-uri'
1559
- if RUBY_VERSION < '2.5'
1560
- open(*args, **kwargs, &blk)
1561
- else
1562
- URI.open(*args, **kwargs, &blk)
1563
- end
1564
- end
1565
-
1566
1547
  def param_guard(action, flag, args:, key: nil, pat: nil, values: nil)
1567
1548
  if args && key
1568
1549
  val = args.fetch(key, nil)
@@ -1577,7 +1558,7 @@ module Squared
1577
1558
  args
1578
1559
  end
1579
1560
 
1580
- def confirm_outdated(pkg, ver, rev, lock: false)
1561
+ def confirm_outdated(pkg, ver, rev, cur = nil, lock: false, col1: 0)
1581
1562
  a = sub_style(case rev
1582
1563
  when 1
1583
1564
  'MAJOR'
@@ -1585,11 +1566,11 @@ module Squared
1585
1566
  'MINOR'
1586
1567
  else
1587
1568
  'PATCH'
1588
- end, styles: theme[:header])
1589
- b = sub_style("#{pkg} #{ver}", styles: theme[:inline])
1569
+ end, styles: (rev == 1 && theme[:major]) || theme[:header])
1570
+ b = sub_style(pkg.ljust(col1), styles: theme[:inline])
1590
1571
  c, d = rev == 1 || lock ? ['y/N', 'N'] : ['Y/n', 'Y']
1591
- e = lock ? " #{sub_style('(locked)', styles: color(:red))}" : ''
1592
- confirm "Upgrade to #{a}? #{b + e} [#{c}] ", d
1572
+ e = lock ? sub_style((cur || 'locked').rjust(7), styles: color(:red)) : cur&.rjust(7)
1573
+ confirm "#{a}: #{b}#{e} #{sub_style(ver.rjust(col1 > 0 ? 10 : 0), styles: theme[:inline])} [#{c}] ", d
1593
1574
  end
1594
1575
 
1595
1576
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil,
@@ -1704,8 +1685,7 @@ module Squared
1704
1685
  end
1705
1686
 
1706
1687
  def semscan(val, fill: true)
1707
- ret = val.scan(SEM_VER).first
1708
- fill ? semver(ret) : ret
1688
+ val.scan(SEM_VER).first.yield_self { |data| fill ? semver(data) : data }
1709
1689
  end
1710
1690
 
1711
1691
  def indexitem(val)
@@ -1721,8 +1701,7 @@ module Squared
1721
1701
  end
1722
1702
 
1723
1703
  def color(val)
1724
- ret = theme[val]
1725
- ret && !ret.empty? ? ret : [val]
1704
+ theme[val].yield_self { |styles| styles && !styles.empty? ? styles : [val] }
1726
1705
  end
1727
1706
 
1728
1707
  def colormap(val)
@@ -1764,39 +1743,22 @@ module Squared
1764
1743
  end
1765
1744
  end
1766
1745
 
1767
- def pwd_set(done = nil, pass: false, from: nil, dryrun: false)
1768
- pwd = Pathname.pwd
1769
- if block_given?
1770
- begin
1771
- if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join <= RUBY_VERSION)
1772
- ret = yield
1773
- else
1774
- Dir.chdir(path)
1775
- ret = yield
1776
- Dir.chdir(pwd)
1777
- end
1778
- rescue StandardError => e
1779
- log&.error e
1780
- unless dryrun
1781
- ret = on(:error, from, e)
1782
- raise if exception && ret != true
1783
- end
1784
- else
1785
- ret
1786
- end
1787
- elsif @pwd == pwd
1788
- @pwd = nil
1789
- pwd unless done
1790
- elsif @pwd
1791
- return unless path == pwd
1792
-
1793
- Dir.chdir(@pwd)
1794
- @pwd = nil
1795
- elsif !done && path != pwd
1796
- @pwd = pwd
1746
+ def pwd_set(pass: false, from: nil)
1747
+ pwd = Dir.pwd
1748
+ if path.to_s == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join <= RUBY_VERSION)
1749
+ ret = yield
1750
+ else
1797
1751
  Dir.chdir(path)
1798
- @pwd
1752
+ ret = yield
1753
+ Dir.chdir(pwd)
1799
1754
  end
1755
+ rescue StandardError => e
1756
+ puts e
1757
+ log&.error e
1758
+ ret = on(:error, from, e)
1759
+ raise if exception && ret != true
1760
+ else
1761
+ ret
1800
1762
  end
1801
1763
 
1802
1764
  def run_set(cmd, val = nil, opts: nil, **)
@@ -1888,16 +1850,6 @@ module Squared
1888
1850
  end
1889
1851
  end
1890
1852
 
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
1853
  def graph_set(val)
1902
1854
  @graph = if val
1903
1855
  as_a(val).map { |s| workspace.prefix ? workspace.task_name(s).to_sym : s.to_sym }.freeze
@@ -1912,6 +1864,25 @@ module Squared
1912
1864
  @exclude = (val ? as_a(val, :to_sym) : []).freeze
1913
1865
  end
1914
1866
 
1867
+ def archive_set(val)
1868
+ @archive = case val
1869
+ when String, Array
1870
+ { uri: val }
1871
+ when Hash
1872
+ val
1873
+ end
1874
+ end
1875
+
1876
+ def theme_set(common)
1877
+ @theme = if !verbose
1878
+ {}
1879
+ elsif common
1880
+ workspace.theme
1881
+ else
1882
+ __get__(:theme)[:project][to_sym] ||= {}
1883
+ end
1884
+ end
1885
+
1915
1886
  def dependfile_set(list)
1916
1887
  @dependindex = list.index { |file| basepath(file).exist? }.tap do |index|
1917
1888
  @dependfile = @path + list[index || 0]
@@ -1934,7 +1905,7 @@ module Squared
1934
1905
  unless @pass.include?(key.to_s) || ws.task_defined?(name, action) || ws.task_exclude?(action, self)
1935
1906
  ws.task_desc(@desc, action)
1936
1907
  task action do
1937
- __send__(key)
1908
+ __send__ key
1938
1909
  end
1939
1910
  end
1940
1911
  next if (items = @children.select { |item| item.task_include?(key) }).empty?
@@ -1946,8 +1917,9 @@ module Squared
1946
1917
  end
1947
1918
 
1948
1919
  def projectpath?(val)
1949
- val = Pathname.new(val).cleanpath
1950
- val.absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?(File.join('..', ''))
1920
+ Pathname.new(val).cleanpath.yield_self do |file|
1921
+ file.absolute? ? file.to_s.start_with?(File.join(path, '')) : !file.to_s.start_with?(File.join('..', ''))
1922
+ end
1951
1923
  end
1952
1924
 
1953
1925
  def checkdir?(val)
@@ -1999,8 +1971,7 @@ module Squared
1999
1971
  return false if workspace.series.chain?(val = task_join(name, action))
2000
1972
  return true if task_invoked?(val) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
2001
1973
 
2002
- val = workspace.series.name_get(action)
2003
- val != action && invoked_sync?(val)
1974
+ workspace.series.name_get(action).yield_self { |name| name != action && invoked_sync?(name) }
2004
1975
  end
2005
1976
 
2006
1977
  def success?(ret)
@@ -2043,7 +2014,7 @@ module Squared
2043
2014
  end
2044
2015
 
2045
2016
  def borderstyle
2046
- ((data = workspace.banner_get(*@ref, group: group)) && data[:border]) || theme[:border]
2017
+ workspace.banner_get(*@ref, group: group)&.border || theme[:border]
2047
2018
  end
2048
2019
 
2049
2020
  def headerstyle
@@ -2056,7 +2027,7 @@ module Squared
2056
2027
  end
2057
2028
 
2058
2029
  Application.implement(Base, base: true)
2059
- Application.attr_banner = Common::SymSet.new(%i[name project path ref group parent])
2030
+ Application.attr_banner = Set.new(%i[name project path ref group parent])
2060
2031
  end
2061
2032
  end
2062
2033
  end