squared 0.6.9 → 0.6.10
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +6 -5
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/project/base.rb +4 -2
- data/lib/squared/workspace/project/docker.rb +27 -14
- data/lib/squared/workspace/project/git.rb +3 -2
- data/lib/squared/workspace/project/node.rb +2 -2
- data/lib/squared/workspace/project/python.rb +12 -9
- data/lib/squared/workspace/project/ruby.rb +10 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51fbe5bab2648700b1859c3cf76d5960e1e6d02084281444e5113a02365ecfc6
|
|
4
|
+
data.tar.gz: f3179ae97b2035233ac01856bd9f775462afb87183da51c3be34eb6357813f61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 368045e7cd2f56b56e95e5d5809346d69e87c41fb3a3b8f33af001844d0d18f63fc5f72abca10d166c7b6af51443dd52ddd2fd9ee72aff1fe67f38e9d7a02526
|
|
7
|
+
data.tar.gz: e750addd94fab922f914ffd75b771c2b5710b64f4375229dc52d7b1129637a5eccd857a7fe39ba51254793f4e3de78c041d73c644accee0a083164abf3132fa4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.10] - 2025-02-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Docker command image action [ls|rm|tag|save] can be filtered.
|
|
8
|
+
- Pip command options were updated to 26.1.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Ruby property autodetect was converted into an accessor.
|
|
13
|
+
- Python property editable was converted into an accessor.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Git task show was completely non-functional due to excessive formatting.
|
|
18
|
+
- Gem command install did not respond to interactive prompt.
|
|
19
|
+
- Docker task clean does not run in parallel without ENV override.
|
|
20
|
+
|
|
3
21
|
## [0.6.9] - 2025-01-07
|
|
4
22
|
|
|
5
23
|
### Added
|
|
@@ -1595,6 +1613,7 @@
|
|
|
1595
1613
|
|
|
1596
1614
|
- Changelog was created.
|
|
1597
1615
|
|
|
1616
|
+
[0.6.10]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.10
|
|
1598
1617
|
[0.6.9]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.9
|
|
1599
1618
|
[0.6.8]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.8
|
|
1600
1619
|
[0.6.7]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.7
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| 2025-01-07 | 0.2.0 | | 3.4.0 |
|
|
12
12
|
| 2025-02-07 | 0.3.0 | | 3.4.1 |
|
|
13
13
|
| 2025-03-06 | 0.4.0 | | 3.4.2 |
|
|
14
|
-
| 2025-06-16 | 0.5.0 | 2.5.0 | 3.4.
|
|
14
|
+
| 2025-06-16 | 0.5.0 | 2.5.0 | 3.4.4 |
|
|
15
15
|
| ---------- | ----- | ----- | ----- |
|
|
16
16
|
| 2025-08-23 | 0.5.5 | | 3.4.5 |
|
|
17
17
|
| 2025-10-31 | 0.6.0 | | 3.4.7 |
|
|
@@ -733,12 +733,13 @@ docker build --no-cache --label=v1 --build-arg="NODE_TAG=24" --build-arg="RUBY_V
|
|
|
733
733
|
| compose | build | TARGET=s |
|
|
734
734
|
| compose | run | VERSION=s |
|
|
735
735
|
| container | commit | REGISTRY=s PLATFORM=s DISABLE_CONTENT_TRUST=0,1 |
|
|
736
|
-
| container | -run -create -exec | ALL=1
|
|
736
|
+
| container | -run -create -exec | ALL=1 FILTER=s |
|
|
737
737
|
| | -update -commit | |
|
|
738
738
|
| image | rm | Y=1 |
|
|
739
739
|
| image | push | TAG=s REGISTRY=s |
|
|
740
740
|
| image | -push | ALL=1 |
|
|
741
|
-
|
|
|
741
|
+
| image | -push -pull | FILTER=s |
|
|
742
|
+
| network | * | ALL=1 FILTER=s |
|
|
742
743
|
|
|
743
744
|
### asdf
|
|
744
745
|
|
|
@@ -806,7 +807,7 @@ REPO_MANIFEST # e.g. latest,nightly,prod
|
|
|
806
807
|
REPO_GROUPS # e.g. base,prod,docs
|
|
807
808
|
REPO_STAGE # 0,1,2,3,4
|
|
808
809
|
REPO_SUBMODULLES # 0,1
|
|
809
|
-
REPO_Y # 0,1
|
|
810
|
+
REPO_Y # 0,1 (bypass interactive prompt)
|
|
810
811
|
REPO_TIMEOUT # confirm dialog (seconds)
|
|
811
812
|
```
|
|
812
813
|
|
|
@@ -835,7 +836,7 @@ Features can be enabled through ENV when calling global tasks such as through *C
|
|
|
835
836
|
| :------------- | :------------- | :------------------------------------------------------- |
|
|
836
837
|
| depend | - | FORCE CI IGNORE_SCRIPTS |
|
|
837
838
|
| outdated | - | U|UPDATE=major|minor|patch DIFF DRY_RUN |
|
|
838
|
-
| publish | - | OTP=s TAG=s ACCESS=0,1,s DRY_RUN
|
|
839
|
+
| publish | - | OTP=s TAG=s ACCESS=0,1,s DRY_RUN Y |
|
|
839
840
|
| depend package | * | PACAKGE_LOCK|LOCKFILE=0 NO_LOCKFILE=1 Y |
|
|
840
841
|
| npm pnpm | depend package | CPU=s OS=s LIBC=s |
|
|
841
842
|
| npm | package | SAVE IGNORE_SCRIPTS STRICT_PEER_DEPS |
|
data/lib/squared/version.rb
CHANGED
|
@@ -1918,8 +1918,10 @@ module Squared
|
|
|
1918
1918
|
args
|
|
1919
1919
|
end
|
|
1920
1920
|
|
|
1921
|
-
def confirm_basic(msg,
|
|
1922
|
-
|
|
1921
|
+
def confirm_basic(msg, hint, default = 'Y', style: :inline, target: @session, prefix: nil, **kwargs)
|
|
1922
|
+
return true if prefix ? option('y', prefix: prefix) : target && option('y', target: target)
|
|
1923
|
+
|
|
1924
|
+
confirm("#{msg} [#{sub_style(hint.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
|
|
1923
1925
|
end
|
|
1924
1926
|
|
|
1925
1927
|
def confirm_outdated(pkg, ver, type, cur = nil, lock: false, col0: 0, col1: 0, col2: nil, col3: 0, col4: 0,
|
|
@@ -282,14 +282,18 @@ module Squared
|
|
|
282
282
|
end
|
|
283
283
|
else
|
|
284
284
|
format_desc(action, flag, case flag
|
|
285
|
-
when :rm, :save then 'id
|
|
286
|
-
when :tag then 'version
|
|
285
|
+
when :rm, :save then 'id,opts*'
|
|
286
|
+
when :tag then 'version'
|
|
287
287
|
else 'opts*,args*'
|
|
288
|
-
end)
|
|
288
|
+
end, before: 'pattern?')
|
|
289
289
|
task flag do |_, args|
|
|
290
290
|
args = args.to_a
|
|
291
|
+
n = args.size
|
|
292
|
+
if (n > 1 || (flag == :ls && n > 0)) && OptionPartition.pattern?(args.first)
|
|
293
|
+
filter = args.shift
|
|
294
|
+
end
|
|
291
295
|
if !args.empty? || flag == :ls
|
|
292
|
-
image
|
|
296
|
+
image(flag, args, filter: filter)
|
|
293
297
|
else
|
|
294
298
|
choice_command flag
|
|
295
299
|
end
|
|
@@ -315,8 +319,8 @@ module Squared
|
|
|
315
319
|
def clean(*, sync: invoked_sync?('clean'), **)
|
|
316
320
|
if runnable?(@clean)
|
|
317
321
|
super
|
|
318
|
-
|
|
319
|
-
image
|
|
322
|
+
elsif sync || option('y', prefix: 'docker')
|
|
323
|
+
image :rm
|
|
320
324
|
end
|
|
321
325
|
end
|
|
322
326
|
|
|
@@ -533,7 +537,7 @@ module Squared
|
|
|
533
537
|
run(from: from)
|
|
534
538
|
end
|
|
535
539
|
|
|
536
|
-
def image(flag, opts = [], sync: true, id: nil, registry: nil)
|
|
540
|
+
def image(flag, opts = [], sync: true, id: nil, registry: nil, filter: nil)
|
|
537
541
|
cmd, opts = docker_session('image', flag, opts: opts)
|
|
538
542
|
op = OptionPartition.new(opts, OPT_DOCKER[:image].fetch(flag, []), cmd, project: self)
|
|
539
543
|
exception = self.exception
|
|
@@ -551,7 +555,7 @@ module Squared
|
|
|
551
555
|
opts.delete(val)
|
|
552
556
|
break
|
|
553
557
|
end
|
|
554
|
-
list_image(:run, from: from) do |val|
|
|
558
|
+
list_image(:run, filter: filter, from: from) do |val|
|
|
555
559
|
container(:run, if name
|
|
556
560
|
opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
|
|
557
561
|
else
|
|
@@ -565,9 +569,7 @@ module Squared
|
|
|
565
569
|
when :rm
|
|
566
570
|
unless id
|
|
567
571
|
if op.empty?
|
|
568
|
-
list_image(:rm, from: from)
|
|
569
|
-
image(:rm, opts, sync: sync, id: val)
|
|
570
|
-
end
|
|
572
|
+
list_image(:rm, filter: filter, from: from) { |val| image(:rm, opts, sync: sync, id: val) }
|
|
571
573
|
else
|
|
572
574
|
op.each { |val| run(cmd.temp(val), sync: sync, from: from) }
|
|
573
575
|
end
|
|
@@ -579,13 +581,16 @@ module Squared
|
|
|
579
581
|
banner = false
|
|
580
582
|
end
|
|
581
583
|
when :tag, :save
|
|
582
|
-
|
|
584
|
+
found = false
|
|
585
|
+
list_image(flag, filter: filter, from: from) do |val|
|
|
583
586
|
op << val
|
|
587
|
+
found = true
|
|
584
588
|
if flag == :tag
|
|
585
589
|
op << tagname("#{project}:#{op.first}")
|
|
586
590
|
break
|
|
587
591
|
end
|
|
588
592
|
end
|
|
593
|
+
raise_error ArgumentError, 'target not specified', hint: flag unless found
|
|
589
594
|
when :push
|
|
590
595
|
id ||= option('tag', ignore: false) || tagmain
|
|
591
596
|
registry ||= op.shift || option('registry') || @registry
|
|
@@ -774,12 +779,20 @@ module Squared
|
|
|
774
779
|
[cmd, status, no]
|
|
775
780
|
end
|
|
776
781
|
|
|
777
|
-
def list_image(flag, cmd = docker_output('image ls -a'), hint: nil, no: true, from: nil)
|
|
782
|
+
def list_image(flag, cmd = docker_output('image ls -a'), filter: nil, hint: nil, no: true, from: nil)
|
|
778
783
|
pwd_set(from: from) do
|
|
779
784
|
index = 1
|
|
780
785
|
all = option('all', prefix: 'docker')
|
|
781
786
|
y = from == :'image:rm' && option('y', prefix: 'docker')
|
|
782
|
-
|
|
787
|
+
filter = env('DOCKER_FILTER', filter).to_s
|
|
788
|
+
pat = if OptionPartition.pattern?(filter)
|
|
789
|
+
Regexp.new(filter)
|
|
790
|
+
elsif filter.match?(/[:_-]$/)
|
|
791
|
+
/\b#{Regexp.escape(filter)}/
|
|
792
|
+
else
|
|
793
|
+
filter = filter.empty? ? '(?:[:_-]|$)' : "[:_-]#{filter}"
|
|
794
|
+
/\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
|
|
795
|
+
end
|
|
783
796
|
IO.popen(cmd.temp('--format=json')).each do |line|
|
|
784
797
|
data = JSON.parse(line)
|
|
785
798
|
id = data['ID']
|
|
@@ -1747,8 +1747,9 @@ module Squared
|
|
|
1747
1747
|
opts << format if format
|
|
1748
1748
|
end
|
|
1749
1749
|
list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
|
|
1750
|
-
op = OptionPartition.new(opts, list, cmd,
|
|
1751
|
-
|
|
1750
|
+
op = OptionPartition.new(opts, list, cmd,
|
|
1751
|
+
project: self,
|
|
1752
|
+
no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
|
|
1752
1753
|
op.append(delim: true)
|
|
1753
1754
|
source(exception: false, banner: flag != :oneline)
|
|
1754
1755
|
end
|
|
@@ -5,7 +5,7 @@ module Squared
|
|
|
5
5
|
module Project
|
|
6
6
|
class Node < Git
|
|
7
7
|
OPT_NPM = {
|
|
8
|
-
common: %w[dry-run=!? loglevel=b include-workspace-root=!? workspaces=!? w|workspace=v].freeze,
|
|
8
|
+
common: %w[dry-run=!? force=!? loglevel=b include-workspace-root=!? workspaces=!? w|workspace=v].freeze,
|
|
9
9
|
install: %w[package-lock-only=!? prefer-dedupe=!? E|save-exact=!? before=q cpu=b libc=b os=b].freeze,
|
|
10
10
|
install_a: %w[audit=! bin-links=! foreground-scripts=!? fund=! ignore-scripts=!? install-links=!?
|
|
11
11
|
package-lock=! strict-peer-deps=!? include=b install-strategy=b omit=b].freeze,
|
|
@@ -1329,7 +1329,7 @@ module Squared
|
|
|
1329
1329
|
|
|
1330
1330
|
def remove_modules(prefix = dependbin)
|
|
1331
1331
|
modules = basepath 'node_modules'
|
|
1332
|
-
return false unless modules.directory? && (
|
|
1332
|
+
return false unless modules.directory? && confirm_basic('Remove?', modules, prefix: prefix)
|
|
1333
1333
|
|
|
1334
1334
|
modules.rmtree
|
|
1335
1335
|
rescue Timeout::Error => e
|
|
@@ -29,14 +29,16 @@ module Squared
|
|
|
29
29
|
install: %w[break-system-packages compile dry-run force-reinstall I|ignore-installed no-compile
|
|
30
30
|
no-warn-conflicts no-warn-script-location U|upgrade user prefix=p report=p root=p
|
|
31
31
|
root-user-action=b t|target=p upgrade-strategy=b].freeze,
|
|
32
|
-
install_a: %w[ignore-requires-python no-index pre
|
|
33
|
-
only-binary=q].freeze,
|
|
34
|
-
install_b: %w[build-constraint check-build-dependencies no-build-isolation no-clean no-deps
|
|
35
|
-
require-hashes use-pep517 c|constraint=p group=q progress-bar=b r|requirement=p
|
|
32
|
+
install_a: %w[ignore-requires-python no-index pre prefer-binary all-releases=b extra-index-url=q
|
|
33
|
+
f|find-links=q i|index-url=q no-binary=q only-binary=q only-final=b uploaded-prior-to=q].freeze,
|
|
34
|
+
install_b: %w[build-constraint check-build-dependencies no-build-isolation no-clean no-deps
|
|
35
|
+
require-hashes use-pep517 c|constraint=p group=q progress-bar=b r|requirement=p
|
|
36
|
+
requirements-from-script=p src=p].freeze,
|
|
36
37
|
install_c: %w[C|config-settings=q e|editable=v].freeze,
|
|
37
38
|
hash: %w[a|algorithm].freeze,
|
|
38
|
-
list: %w[e|editable exclude-editable include-editable l|local no-index not-required o|outdated pre
|
|
39
|
-
user exclude=b extra-index-url=q format=b f|find-links=q
|
|
39
|
+
list: %w[e|editable exclude-editable include-editable l|local no-index not-required o|outdated pre
|
|
40
|
+
prefer-binary u|uptodate user all-releases=b exclude=b extra-index-url=q format=b f|find-links=q
|
|
41
|
+
i|index-url=q no-binary=q only-binary=q only-final=b path=p].freeze,
|
|
40
42
|
lock: %w[o|output=p].freeze,
|
|
41
43
|
show: %w[f|files].freeze,
|
|
42
44
|
uninstall: %w[break-system-packages y|yes r|requirement=p root-user-action=b].freeze,
|
|
@@ -73,7 +75,7 @@ module Squared
|
|
|
73
75
|
debug: %w[platform].freeze,
|
|
74
76
|
install: %w[C config-settings c constraint extra-index-url no-binary only-binary platform
|
|
75
77
|
r requirement].freeze,
|
|
76
|
-
list: %w[exclude extra-index-url].freeze
|
|
78
|
+
list: %w[exclude extra-index-url no-binary only-binary].freeze
|
|
77
79
|
}.freeze
|
|
78
80
|
}.freeze
|
|
79
81
|
private_constant :DEP_PYTHON, :DIR_PYTHON, :OPT_PYTHON, :OPT_PIP, :OPT_POETRY, :OPT_PDM, :OPT_HATCH, :OPT_TWINE,
|
|
@@ -99,7 +101,8 @@ module Squared
|
|
|
99
101
|
end
|
|
100
102
|
end
|
|
101
103
|
|
|
102
|
-
attr_reader :venv
|
|
104
|
+
attr_reader :venv
|
|
105
|
+
attr_accessor :editable
|
|
103
106
|
|
|
104
107
|
def initialize(*, editable: '.', asdf: 'python', **kwargs)
|
|
105
108
|
super
|
|
@@ -249,7 +252,7 @@ module Squared
|
|
|
249
252
|
if args.empty?
|
|
250
253
|
args = readline('Enter command', force: true).split(' ', 2)
|
|
251
254
|
elsif args.size == 1 && !option('interactive', equals: '0', prefix: ref)
|
|
252
|
-
args << readline('Enter arguments', force: false)
|
|
255
|
+
args << readline('Enter arguments', force: false) unless args.first.include?(' ')
|
|
253
256
|
end
|
|
254
257
|
venv_init
|
|
255
258
|
run args.join(' ')
|
|
@@ -187,6 +187,7 @@ module Squared
|
|
|
187
187
|
})
|
|
188
188
|
|
|
189
189
|
attr_reader :gemdir
|
|
190
|
+
attr_accessor :autodetect
|
|
190
191
|
|
|
191
192
|
def initialize(*, autodetect: false, gemspec: nil, steep: 'Steepfile', rubocop: '.rubocop.yml', asdf: 'ruby',
|
|
192
193
|
**kwargs)
|
|
@@ -199,7 +200,7 @@ module Squared
|
|
|
199
200
|
initialize_env(**kwargs)
|
|
200
201
|
end
|
|
201
202
|
dependfile_set GEMFILE
|
|
202
|
-
|
|
203
|
+
self.autodetect = autodetect
|
|
203
204
|
@gemfile = if gemspec == false
|
|
204
205
|
false
|
|
205
206
|
elsif gemspec
|
|
@@ -207,7 +208,7 @@ module Squared
|
|
|
207
208
|
end
|
|
208
209
|
@steepfile = basepath! steep if steep
|
|
209
210
|
@rubocopfile = Pathname.new(rubocop).realpath rescue basepath!(Dir.home, '.rubocop.yml') if rubocop
|
|
210
|
-
return unless rakefile && @output[0].nil? && @copy.nil? && !version &&
|
|
211
|
+
return unless rakefile && @output[0].nil? && @copy.nil? && !version && !self.autodetect
|
|
211
212
|
|
|
212
213
|
begin
|
|
213
214
|
File.foreach(rakefile) do |line|
|
|
@@ -1106,6 +1107,7 @@ module Squared
|
|
|
1106
1107
|
when :dependency, :environment, :list, :search, :specification, :which
|
|
1107
1108
|
op.concat(args)
|
|
1108
1109
|
end
|
|
1110
|
+
ia = op.remove(':')
|
|
1109
1111
|
op.each do |opt|
|
|
1110
1112
|
if gems && !opt.start_with?('-') && !opt.match?(GEMNAME)
|
|
1111
1113
|
op.errors << opt
|
|
@@ -1169,7 +1171,7 @@ module Squared
|
|
|
1169
1171
|
end
|
|
1170
1172
|
when :install, :uninstall, :pristine
|
|
1171
1173
|
if flag == :install
|
|
1172
|
-
post = if
|
|
1174
|
+
post = if ia
|
|
1173
1175
|
op.concat(args)
|
|
1174
1176
|
readline('Enter command [args]', force: true)
|
|
1175
1177
|
elsif op.empty?
|
|
@@ -1187,14 +1189,14 @@ module Squared
|
|
|
1187
1189
|
else
|
|
1188
1190
|
op.clear
|
|
1189
1191
|
end
|
|
1190
|
-
elsif (n = op.index { |val| val.match?(/(\A|[
|
|
1192
|
+
elsif (n = op.index { |val| val.match?(/(\A|[\w.-])@\d/) })
|
|
1191
1193
|
name = op.remove_at(n)
|
|
1192
1194
|
pre, ver = if (n = name.index('@')) == 0
|
|
1193
1195
|
[gemname, name[1..-1]]
|
|
1194
1196
|
else
|
|
1195
1197
|
[name[0, n], name[n.succ..-1]]
|
|
1196
1198
|
end
|
|
1197
|
-
op.adjoin(pre,
|
|
1199
|
+
op.adjoin(pre, quote_option('version', ver))
|
|
1198
1200
|
.clear
|
|
1199
1201
|
end
|
|
1200
1202
|
if flag == :install
|
|
@@ -1502,7 +1504,7 @@ module Squared
|
|
|
1502
1504
|
def copy?
|
|
1503
1505
|
return true if @copy.is_a?(Hash) ? copy[:into] : super
|
|
1504
1506
|
return gemdir? if gemdir
|
|
1505
|
-
return false unless
|
|
1507
|
+
return false unless autodetect
|
|
1506
1508
|
|
|
1507
1509
|
set = lambda do |val, path|
|
|
1508
1510
|
base = Pathname.new(path.strip)
|
|
@@ -1515,7 +1517,7 @@ module Squared
|
|
|
1515
1517
|
end
|
|
1516
1518
|
if version
|
|
1517
1519
|
begin
|
|
1518
|
-
case
|
|
1520
|
+
case autodetect
|
|
1519
1521
|
when 'rvm'
|
|
1520
1522
|
pwd_set { `rvm info homes` }[/^\s+gem:\s+"(.+)"$/, 1]
|
|
1521
1523
|
when 'rbenv'
|
|
@@ -1573,7 +1575,7 @@ module Squared
|
|
|
1573
1575
|
log.error e
|
|
1574
1576
|
self.version = nil
|
|
1575
1577
|
@gemdir = nil
|
|
1576
|
-
|
|
1578
|
+
self.autodetect = false
|
|
1577
1579
|
else
|
|
1578
1580
|
gemdir?
|
|
1579
1581
|
end
|