squared 0.5.3 → 0.5.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54118de98db0adf55417ad35cbc3c118bba709c8bbb98b9d74acccaf7a30621e
4
- data.tar.gz: 13f6ac344ecdc68f724c10a967f0eba1de6cd0332157abe0c9079db9dba1b9d0
3
+ metadata.gz: 5f2dec97bece08fd8a158d8db320fbcb507e0e35c0d05802763616ed8c66a943
4
+ data.tar.gz: 5049853f41262335db8d2ff6c86f7c8b583e87ef868f18970f12e239aa40c58e
5
5
  SHA512:
6
- metadata.gz: cfbfbd1b2a5fcd376fceafe53fa77789726704d6cdf7f3d919c6381e04f89e2fc3888adfb38e819bbeba7bb51cd7f7f0c08e4fe797d16ee688752dddb9ad6b68
7
- data.tar.gz: 4ffd9c7e92c7d357c6e877fa78ca850b202585721001a2c58dbc5851eac292abad59ab67f12fc8eab5ef7706bf3cd1d8db7aaf4ee9b169f0cebf38dfe1016b99
6
+ metadata.gz: 0aa7c2688ecaec61eb92d696dc4a10cd95de39799fad9a295561b33f933093335a7ddc5109ef56dab7b734a48505ef882c72d17a47e847f78fd312448a3210e0
7
+ data.tar.gz: 7f174f6e1ac139e4deaf97445660b4c021822d97ec74bc9997ce7932b02f88c6abc2b822f3b0b4f2213144c0aec66e9c6d52d40f0cff2d8ccaf7d36b091cf1e0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.4] - 2025-08-09
4
+
5
+ ### Changed
6
+
7
+ - Shellwords gem is no longer globally required.
8
+
9
+ ## [0.4.17] - 2025-08-09
10
+
11
+ ### Added
12
+
13
+ - Node repos can be initialized with specific package manager.
14
+ - Docker Engine options were updated to 28.0.
15
+ - Git commands pull and fetch action all was implemented.
16
+ - Git command reset action undo was implemented.
17
+ - Python command pip action upgrade was implemented.
18
+ - Git commands refs and files can use both pathspec and pattern.
19
+ - Git command submodule was implemented.
20
+ - Git command status was implemented.
21
+ - Git command switch action branch was implemented.
22
+
23
+ ### Changed
24
+
25
+ - Project task events can be assigned to a single proc.
26
+ - Ruby task copy uses require paths from gem specification.
27
+ - Docker task build:bake was renamed bake:build.
28
+ - Project global tasks can be hidden and still expose subtasks.
29
+ - Repo command line options can be overriden with class method.
30
+ - Repo task all variable REPO_DRYRUN was replaced with REPO_STAGE.
31
+ - Repo command sync option no-fail was renamed fail.
32
+
33
+ ### Fixed
34
+
35
+ - Gem command outdated did not work outside main project folder.
36
+ - Node command bump did not reset major and minor trailing digits.
37
+ - Docker image status message was displayed backwards.
38
+
3
39
  ## [0.5.3] - 2025-07-27
4
40
 
5
41
  ### Fixed
@@ -908,10 +944,12 @@
908
944
 
909
945
  - Changelog was created.
910
946
 
947
+ [0.5.4]: https://github.com/anpham6/squared/releases/tag/v0.5.4-ruby
911
948
  [0.5.3]: https://github.com/anpham6/squared/releases/tag/v0.5.3-ruby
912
949
  [0.5.2]: https://github.com/anpham6/squared/releases/tag/v0.5.2-ruby
913
950
  [0.5.1]: https://github.com/anpham6/squared/releases/tag/v0.5.1-ruby
914
951
  [0.5.0]: https://github.com/anpham6/squared/releases/tag/v0.5.0-ruby
952
+ [0.4.17]: https://github.com/anpham6/squared/releases/tag/v0.4.17-ruby
915
953
  [0.4.16]: https://github.com/anpham6/squared/releases/tag/v0.4.16-ruby
916
954
  [0.4.15]: https://github.com/anpham6/squared/releases/tag/v0.4.15-ruby
917
955
  [0.4.14]: https://github.com/anpham6/squared/releases/tag/v0.4.14-ruby
data/README.ruby.md CHANGED
@@ -89,7 +89,7 @@ Workspace::Application
89
89
  .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
90
90
  .add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
91
91
  .add("pi-r2", "pir2", copy: { from: :npm, also: %i[squared express], files: ["LICENSE", ["README.md.ruby", "README.md"]] }) # Uses dist files from NPM package spec
92
- .add("squared", script: ["build:stage1", "build:stage2"], group: "app") do # Copy target (main)
92
+ .add("squared", init: 'pnpm', script: ["build:stage1", "build:stage2"], group: "app") do # Use pnpm/yarn/berry for depend + Copy target (main)
93
93
  # Repo (global)
94
94
  as(:run, "build:dev", "dev") # npm run build:dev -> npm run dev
95
95
  as(:run, { "build:dev": "dev", "build:prod": "prod" })
@@ -500,11 +500,11 @@ Most project classes will inherit from `Git` which enables these tasks:
500
500
  | checkout | checkout | commit branch track detach path |
501
501
  | commit | commit | add all amend amend-orig fixup |
502
502
  | diff | diff | head cached branch files between contain |
503
- | fetch | fetch | origin remote |
503
+ | fetch | fetch | origin remote all |
504
504
  | files | ls-files | cached modified deleted others |
505
- | git | | add clean mv rm revert |
505
+ | git | | add blame clean mv rm revert |
506
506
  | merge | merge | commit no-commit send |
507
- | pull | pull | origin remote |
507
+ | pull | pull | origin remote all |
508
508
  | rebase | rebase | branch onto send |
509
509
  | refs | ls-remote --refs | heads tags remote |
510
510
  | reset | reset | commit index patch mode |
@@ -512,7 +512,7 @@ Most project classes will inherit from `Git` which enables these tasks:
512
512
  | rev | rev | commit output |
513
513
  | show | show | format oneline |
514
514
  | stash | stash | push pop apply branch drop clear list |
515
- | switch | switch | create detach merge |
515
+ | switch | switch | branch create detach |
516
516
  | tag | tag | add sign delete list |
517
517
 
518
518
  You can disable all of them at once using the `exclude` property.
@@ -625,6 +625,8 @@ LOG_LEVEL # See gem "logger"
625
625
 
626
626
  ### Git
627
627
 
628
+ * Version: 2.50
629
+
628
630
  ```sh
629
631
  GIT_OPTIONS=q,strategy=ort # all
630
632
  GIT_OPTIONS_${NAME}=v,ff # project only
@@ -634,15 +636,15 @@ GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
634
636
 
635
637
  | Command | Flag | ENV |
636
638
  | :--------- | :---------------- | :-------------------------------------------------------------------- |
637
- | branch | create | TRACK=0,1,s FORCE |
638
- | branch | move copy | FORCE |
639
+ | branch | create | TRACK=0,1,s F|FORCE |
640
+ | branch | move copy | F|FORCE |
639
641
  | branch | set delete | COUNT=n |
640
642
  | branch | global | SYNC |
641
643
  | checkout | branch | DETACH TRACK=s COUNT=n |
642
644
  | checkout | detach | REFLOG=1 |
643
645
  | checkout | track | COUNT=n |
644
646
  | checkout | global path | HEAD=s PATHSPEC=s |
645
- | checkout | * | FORCE MERGE |
647
+ | checkout | * | F|FORCE MERGE |
646
648
  | clone | * | DEPTH=n ORIGIN=s BRANCH=s REVISION=s LOCAL=0,1 |
647
649
  | commit | * | UPSTREAM=s DRY_RUN EDIT=0 M|MESSAGE=s |
648
650
  | diff | -between -contain | MERGE_BASE |
@@ -650,13 +652,13 @@ GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
650
652
  | diff | * | PATHSPEC=s |
651
653
  | fetch | -remote | ALL |
652
654
  | fetch | remote | REFSPEC=s |
653
- | fetch | * | FORCE RECURSE_SUBMODULES=0,1,s |
655
+ | fetch | * | F|FORCE RECURSE_SUBMODULES=0,1,s |
654
656
  | git | rm | PATHSPEC=s |
655
657
  | log | * | PATHSPEC=s |
656
658
  | pull | rebase | AUTOSTASH |
657
659
  | pull | remote | REFSPEC=s |
658
660
  | pull | -remote | ALL |
659
- | pull | * | REBASE=0,1 FORCE RECURSE_SUBMODULES=0,1,s |
661
+ | pull | * | REBASE=0,1 F|FORCE RECURSE_SUBMODULES=0,1,s |
660
662
  | rebase | branch | HEAD=s |
661
663
  | rebase | onto | INTERACTIVE I HEAD=s |
662
664
  | reset | mode (mixed) | N REFRESH=0 |
@@ -670,14 +672,16 @@ GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
670
672
  | status | global | BRANCH LONG IGNORE_SUBMODULES=s,0-3 PATHSPEC=s |
671
673
  | switch | detach | REFLOG=1 |
672
674
  | switch | -detach | HEAD=s |
673
- | switch | * | FORCE |
675
+ | switch | * | F|FORCE |
674
676
  | tag | add | SIGN FORCE HEAD=s M|MESSAGE=s |
675
- | tag | sign | FORCE HEAD=s M|MESSAGE=s |
677
+ | tag | sign | F|FORCE HEAD=s M|MESSAGE=s |
676
678
  | tag | delete | COUNT=n |
677
679
  | rev | commit branch | HEAD=s |
678
680
 
679
681
  ### Docker
680
682
 
683
+ * Version: 28.3
684
+
681
685
  ```sh
682
686
  DOCKER_OPTIONS=q,no-cache # all
683
687
  DOCKER_OPTIONS_${NAME}=v,no-cache=false # project only (override)
@@ -715,7 +719,7 @@ REPO_WARN # 0,1
715
719
  REPO_SYNC # 0,1
716
720
  REPO_URL # manifest repository
717
721
  REPO_MANIFEST # e.g. latest,nightly,prod
718
- REPO_DRYRUN # 0,1,2
722
+ REPO_STAGE # 0,1,2,3
719
723
  REPO_TIMEOUT # confirm dialog (seconds)
720
724
  ```
721
725
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'shellwords'
4
3
  require 'rake'
5
4
 
6
5
  module Squared
@@ -31,13 +30,19 @@ module Squared
31
30
 
32
31
  "#{$1}=%s" % if $2.include?(' ')
33
32
  shell_quote($2, option: false)
33
+ elsif Rake::Win32.windows?
34
+ $2
34
35
  else
35
- Rake::Win32.windows? ? $2 : Shellwords.escape($2)
36
+ require 'shellwords'
37
+ Shellwords.escape($2)
36
38
  end
37
39
  elsif Rake::Win32.windows?
38
40
  quote ? shell_quote(val, double: double, force: force) : val
41
+ elsif val.empty?
42
+ ''
39
43
  else
40
- val.empty? ? '' : Shellwords.escape(val)
44
+ require 'shellwords'
45
+ Shellwords.escape(val)
41
46
  end
42
47
  end
43
48
 
@@ -12,9 +12,9 @@ module Squared
12
12
  if RUBY_ENGINE == 'jruby' && Rake::Win32.windows?
13
13
  e = kwargs[:exception]
14
14
  if (dir = kwargs[:chdir]) && ((pwd = Dir.pwd) != dir)
15
- Dir.chdir(dir)
15
+ Dir.chdir dir
16
16
  ret = Kernel.send(name, *args)
17
- Dir.chdir(pwd)
17
+ Dir.chdir pwd
18
18
  else
19
19
  ret = Kernel.send(name, *args)
20
20
  end
@@ -59,7 +59,7 @@ module Squared
59
59
  soft = 0
60
60
  subdir.each do |dir, files|
61
61
  if link
62
- files.dup.yield_self do |items|
62
+ files.dup.tap do |items|
63
63
  files.clear
64
64
  items.each do |file|
65
65
  if file.exist?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.5.3'
4
+ VERSION = '0.5.4'
5
5
  end
@@ -26,7 +26,7 @@ module Squared
26
26
  next unless base || obj < impl_project
27
27
 
28
28
  if base
29
- @impl_project = obj
29
+ self.impl_project = obj
30
30
  impl_series.base_set(obj)
31
31
  else
32
32
  kind_project.prepend(obj)
@@ -39,7 +39,7 @@ module Squared
39
39
  impl_series.alias(*args)
40
40
  end
41
41
  if (args = obj.bannerargs)
42
- @attr_banner.merge(args)
42
+ attr_banner.merge(args)
43
43
  end
44
44
  end
45
45
  end
@@ -83,7 +83,7 @@ module Squared
83
83
  basename = @home.basename.to_s
84
84
  if main
85
85
  @main = main.to_s.freeze
86
- @home += @main unless @main == basename || (windows? && @main.downcase == basename.downcase)
86
+ @home += @main unless @main == basename || (windows? && @main.casecmp?(basename))
87
87
  else
88
88
  @main = basename.freeze
89
89
  end
@@ -686,9 +686,14 @@ module Squared
686
686
  private
687
687
 
688
688
  def __build__(default: nil, **)
689
- return unless default && task_defined?(out = task_name(default))
690
-
691
- task Rake.application.default_task_name => out
689
+ unless task_defined?('squared:version')
690
+ task 'squared:version' do
691
+ puts Squared::VERSION
692
+ end
693
+ end
694
+ if default && task_defined?(out = task_name(default))
695
+ task Rake.application.default_task_name => out
696
+ end
692
697
  end
693
698
 
694
699
  def __chain__(*)
@@ -21,8 +21,8 @@ module Squared
21
21
  VAR_SET = %i[parent global script index envname desc dependfile dependindex theme archive env dev prod graph
22
22
  pass only exclude].freeze
23
23
  BLK_SET = %i[run depend doc lint test copy clean].freeze
24
- SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
25
- URI_SCHEME = %r{^([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
24
+ SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?-?(\S+)?\b/.freeze
25
+ URI_SCHEME = %r{\A([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
26
26
  TASK_METADATA = Rake::TaskManager.record_task_metadata
27
27
  private_constant :VAR_SET, :BLK_SET, :SEM_VER, :URI_SCHEME, :TASK_METADATA
28
28
 
@@ -213,7 +213,7 @@ module Squared
213
213
  def initialize_env(dev: nil, prod: nil, **)
214
214
  @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
215
215
  @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
216
- if @output[2] != false && (val = env('BUILD', suffix: 'ENV'))
216
+ unless @output[2] == false || !(val = env('BUILD', suffix: 'ENV'))
217
217
  @output[2] = parse_json(val, hint: "BUILD_#{@envname}_ENV") || @output[2]
218
218
  end
219
219
  unless @output[0] == false || @output[0].is_a?(Array)
@@ -658,7 +658,7 @@ module Squared
658
658
  when 128, 'sha512'
659
659
  Digest::SHA512
660
660
  else
661
- raise_error("invalid checksum: #{digest}", hint: name)
661
+ raise_error "invalid checksum: #{digest}"
662
662
  end
663
663
  end
664
664
  if (val = env('HEADERS')) && (val = parse_json(val, hint: "HEADERS_#{@envname}"))
@@ -991,7 +991,7 @@ module Squared
991
991
  end
992
992
 
993
993
  def run(cmd = @session, var = nil, exception: self.exception, sync: true, from: nil, banner: true, chdir: path,
994
- interactive: nil, **)
994
+ interactive: nil, hint: nil, **)
995
995
  unless cmd
996
996
  warn log_message(Logger::WARN, 'no command given', subject: project, hint: from || 'unknown', pass: true)
997
997
  return
@@ -1017,7 +1017,7 @@ module Squared
1017
1017
  log&.warn "ENV discarded: #{var}" if var
1018
1018
  task_invoke(cmd, exception: exception, warning: warning?)
1019
1019
  else
1020
- print_item format_banner(cmd, banner: banner) if sync
1020
+ print_item format_banner(hint ? "#{cmd} (#{hint})" : cmd, banner: banner) if sync
1021
1021
  if var != false && (pre = runenv)
1022
1022
  case pre
1023
1023
  when Hash
@@ -1256,7 +1256,7 @@ module Squared
1256
1256
  def session_done(cmd)
1257
1257
  return cmd unless cmd.respond_to?(:done)
1258
1258
 
1259
- raise_error('no args added', hint: cmd.first || name) unless cmd.size > 1
1259
+ raise_error('no args added', hint: cmd.first) unless cmd.size > 1
1260
1260
  @session = nil if cmd == @session
1261
1261
  cmd.done
1262
1262
  end
@@ -1410,7 +1410,7 @@ module Squared
1410
1410
  unless items.empty?
1411
1411
  pad = items.size.to_s.size
1412
1412
  items.each_with_index do |val, i|
1413
- next unless reg.empty? || reg.any? { |pat| val[0].match?(pat) }
1413
+ next unless matchany?(val[0], reg)
1414
1414
 
1415
1415
  out << ('%*d. %s' % [pad, i.succ, each ? each.call(val) : val[0]])
1416
1416
  end
@@ -1513,11 +1513,11 @@ module Squared
1513
1513
  list.flatten.each do |opt|
1514
1514
  next unless (val = option(opt, **kwargs))
1515
1515
 
1516
- return target << (if flag
1517
- shell_option(opt, equals ? val : nil, quote: quote, escape: escape, force: force)
1518
- else
1519
- shell_quote val
1520
- end)
1516
+ return target << if flag
1517
+ shell_option(opt, equals ? val : nil, quote: quote, escape: escape, force: force)
1518
+ else
1519
+ shell_quote val
1520
+ end
1521
1521
  end
1522
1522
  nil
1523
1523
  end
@@ -1726,6 +1726,17 @@ module Squared
1726
1726
  files.map { |val| val == '.' ? '.' : shell_quote(path + val) }
1727
1727
  end
1728
1728
 
1729
+ def matchmap(list, prefix = nil)
1730
+ list.map do |val|
1731
+ if val.is_a?(Regexp)
1732
+ val
1733
+ else
1734
+ val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1735
+ Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1736
+ end
1737
+ end
1738
+ end
1739
+
1729
1740
  def semver(val)
1730
1741
  return val if val[3]
1731
1742
 
@@ -1743,12 +1754,13 @@ module Squared
1743
1754
  end
1744
1755
 
1745
1756
  def semcmp(val, other)
1746
- a, b = [val, other].map! { |ver| ver.match(SEM_VER) }
1747
- return 1 unless a
1748
- return -1 unless b
1749
- return 0 if a[0] == b[0]
1757
+ return 0 if val == other
1758
+
1759
+ a, b = [val, other].map! { |ver| ver.scan(SEM_VER) }
1760
+ return -1 if b.empty?
1761
+ return 1 if a.empty?
1750
1762
 
1751
- a, b = [a, b].map! { |c| [c[1], c[3], c[5] || '0'] }
1763
+ a, b = [a.first, b.first].map! { |c| [c[0], c[2], c[4] || '0', c[5] ? '-1' : '0'] }
1752
1764
  a.each_with_index do |c, index|
1753
1765
  next if c == (d = b[index])
1754
1766
 
@@ -1757,6 +1769,10 @@ module Squared
1757
1769
  0
1758
1770
  end
1759
1771
 
1772
+ def semgte?(val, other)
1773
+ semcmp(val, other) != 1
1774
+ end
1775
+
1760
1776
  def indexitem(val)
1761
1777
  [$1.to_i, $2 && $2[1..-1]] if val =~ /\A[=^#{indexchar}](\d+)(:.+)?\z/
1762
1778
  end
@@ -1800,7 +1816,7 @@ module Squared
1800
1816
  def on(event, from, *args, **kwargs)
1801
1817
  return unless from && @events.key?(event)
1802
1818
 
1803
- @events[event][from]&.each do |obj|
1819
+ Array(@events[event][from]).each do |obj|
1804
1820
  target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
1805
1821
  [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
1806
1822
  else
@@ -1826,25 +1842,22 @@ module Squared
1826
1842
  print_error(err, pass: pass) unless ret
1827
1843
  end
1828
1844
 
1829
- def pwd_set(pass: false, from: nil)
1830
- pass = semscan(pass).join <= RUBY_VERSION if pass.is_a?(String)
1845
+ def pwd_set(pass: false, dryrun: false, from: nil)
1831
1846
  pwd = Dir.pwd
1832
- if (path.to_s == pwd || pass == true) && (workspace.mri? || !workspace.windows?)
1833
- ret = yield
1834
- else
1835
- Dir.chdir(path)
1836
- ret = yield
1837
- Dir.chdir(pwd)
1838
- end
1847
+ return yield if (path.to_s == pwd || pass == true) && (workspace.mri? || !workspace.windows?)
1848
+
1849
+ Dir.chdir path
1850
+ ret = yield
1851
+ Dir.chdir pwd
1839
1852
  rescue StandardError => e
1840
- on_error e, from
1853
+ on_error(e, from, dryrun: dryrun)
1841
1854
  else
1842
1855
  ret
1843
1856
  end
1844
1857
 
1845
1858
  def run_set(cmd, val = nil, opts: nil, **)
1846
- diso = @output[1] == false && !@output[0].nil?
1847
- dise = @output[2] == false
1859
+ noopt = @output[1] == false && !@output[0].nil?
1860
+ noenv = @output[2] == false
1848
1861
  parse = lambda do |data|
1849
1862
  ret = []
1850
1863
  if data[:command]
@@ -1864,8 +1877,8 @@ module Squared
1864
1877
  case cmd
1865
1878
  when Array
1866
1879
  @output = if cmd.all? { |data| data.is_a?(Hash) }
1867
- diso = false
1868
- dise = false
1880
+ noopt = false
1881
+ noenv = false
1869
1882
  cmd.map { |data| parse.call(data) }
1870
1883
  else
1871
1884
  cmd.dup
@@ -1876,14 +1889,14 @@ module Squared
1876
1889
  else
1877
1890
  @output[0] = cmd
1878
1891
  end
1879
- unless diso
1892
+ unless noopt
1880
1893
  if opts == false
1881
1894
  @output[1] = false
1882
1895
  elsif opts && opts != true
1883
1896
  @output[1] = opts
1884
1897
  end
1885
1898
  end
1886
- return if dise
1899
+ return if noenv
1887
1900
 
1888
1901
  if val.is_a?(Hash)
1889
1902
  @output[2] = val
@@ -1986,6 +1999,10 @@ module Squared
1986
1999
  @only ? !@only.include?(key) : @pass.include?(key)
1987
2000
  end
1988
2001
 
2002
+ def matchany?(val, list, empty: true)
2003
+ list.empty? ? empty : list.any? { |pat| val.match?(pat) }
2004
+ end
2005
+
1989
2006
  def projectpath?(val)
1990
2007
  Pathname.new(val).cleanpath.yield_self do |file|
1991
2008
  file.absolute? ? file.to_s.start_with?(File.join(path, '')) : !file.to_s.start_with?(File.join('..', ''))