itunes_store_transporter 0.2.0 → 0.2.1
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/Changes +5 -0
- data/README.rdoc +10 -25
- data/lib/itunes/store/transporter.rb +1 -1
- data/lib/itunes/store/transporter/command.rb +0 -1
- data/lib/itunes/store/transporter/command/lookup.rb +3 -0
- data/lib/itunes/store/transporter/command/option.rb +1 -0
- data/lib/itunes/store/transporter/command/schema.rb +3 -0
- data/lib/itunes/store/transporter/command/status.rb +2 -0
- data/lib/itunes/store/transporter/command/upload.rb +2 -0
- data/lib/itunes/store/transporter/command/verify.rb +2 -0
- data/lib/itunes/store/transporter/itms_transporter.rb +2 -1
- data/lib/itunes/store/transporter/version.rb +1 -1
- data/spec/command_spec.rb +15 -3
- data/spec/errors_spec.rb +18 -18
- data/spec/output_parser_spec.rb +15 -4
- data/spec/transporter_spec.rb +1 -1
- metadata +7 -13
- data/spec/xml_status_spec.rb~ +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc9637b7cc0896807aa9592f53c742651f264280
|
4
|
+
data.tar.gz: e3510e67c8d7dab12965308d15d38aad958834b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53d6ea94ed09ff77c56bfe036171b1d30c6fa4644ce7002f4871b85c047f180fc6ac3263745f53919a322ebf1547b3c6ae7f77a8d5def24b9fb783b7cb9edcff
|
7
|
+
data.tar.gz: 213aaac35b156e73afd63e6ce961f4d212b14adfb74ee6c38132c180e03117ed564e93237e167c5019037a78f17f2c6b93f1cec7d194de2f8f84f3649e9c73fb
|
data/Changes
CHANGED
data/README.rdoc
CHANGED
@@ -11,7 +11,7 @@ Upload and manage your assets in the iTunes Store using the iTunes Store's Trans
|
|
11
11
|
require "itunes/store/transporter"
|
12
12
|
|
13
13
|
itms = iTunes::Store::Transporter.new(:username => "SomeUser",
|
14
|
-
:shortname => "shrt",
|
14
|
+
:shortname => "shrt", # or :itc_provider, if you have no shortname
|
15
15
|
:password => "w3c@llYoU!")
|
16
16
|
|
17
17
|
itms.upload("/path/to/yourpackage.itmsp")
|
@@ -50,35 +50,16 @@ that it can send email notifications and allows one to set global/per-command de
|
|
50
50
|
|
51
51
|
=== Locating iTMSTransporter
|
52
52
|
|
53
|
-
If the +iTMSTransporter+ cannot be found in {one your platform's known locations}[https://github.com/sshaw/itunes_store_transporter/
|
53
|
+
If the +iTMSTransporter+ cannot be found in {one your platform's known locations}[https://github.com/sshaw/itunes_store_transporter/blob/fcb4f0f9ada4a45764160beac6a049b0d419c8a9/lib/itunes/store/transporter/shell.rb#L31] you must specify it when creating an instance of <code>iTunes::Store::Transporter</code> via {the <code>:path</code> option}[http://www.rubydoc.info/gems/itunes_store_transporter/ITunes/Store/Transporter/ITMSTransporter#initialize-instance_method].
|
54
54
|
|
55
55
|
If you're using the bundled +itms+ command you must use its +--path+ option or specify the path in the itms config file. See the +itms+ section for more info.
|
56
56
|
|
57
|
-
=== Running on Windows
|
58
|
-
|
59
|
-
On Windows +iTMSTransporter+ is called via the +iTMSTransporter.CMD+ batch file. This file does not handle the
|
60
|
-
+iTMSTransporter+'s exit status correctly, causing <code>iTunes::Store::Transporter</code> to report everything as a success.
|
61
|
-
|
62
|
-
This can be fixed by modifying +iTMSTransporter.CMD+ (note that the following does not mimic the batch file exactly):
|
63
|
-
|
64
|
-
...
|
65
|
-
|
66
|
-
call iTMSTransporter\iTMSTransporter %*
|
67
|
-
|
68
|
-
REM Add this line:
|
69
|
-
set exited=%errorlevel%
|
70
|
-
|
71
|
-
cd %olddir%
|
72
|
-
|
73
|
-
REM Add this line too:
|
74
|
-
exit /b %exited%
|
75
|
-
|
76
57
|
=== Using itms
|
77
58
|
|
78
59
|
<code>itms COMMAND [OPTIONS] [PACKAGE]</code>
|
79
60
|
|
80
|
-
* +COMMAND+ - The command to run, which can be any one of {the <code>iTunes::Store::Transporter</code> methods}[http://
|
81
|
-
* +OPTIONS+ - These are quivalent to the given +COMMAND+'s options except they must be given in a strict long option format. For example <code>:apple_id => "X123"</code> would be <code>--apple-id=X123</code>. Boolean options can be negated with the <code>--no-</code> prefix. For more info see {each command's options}[http://
|
61
|
+
* +COMMAND+ - The command to run, which can be any one of {the <code>iTunes::Store::Transporter</code> methods}[http://sshaw.github.io/itunes_store_transporter/classes/ITunes/Store/Transporter/ITMSTransporter.html]
|
62
|
+
* +OPTIONS+ - These are quivalent to the given +COMMAND+'s options except they must be given in a strict long option format. For example <code>:apple_id => "X123"</code> would be <code>--apple-id=X123</code>. Boolean options can be negated with the <code>--no-</code> prefix. For more info see {each command's options}[http://sshaw.github.io/itunes_store_transporter/classes/ITunes/Store/Transporter/ITMSTransporter.html].
|
82
63
|
* +PACKAGE+ - The package or directory to operate on, if required by the command
|
83
64
|
|
84
65
|
<b>Note that options with a value must contain the equals character</b> (<code>"="</code>).
|
@@ -166,10 +147,10 @@ As you can see, command options are turned into template variables.
|
|
166
147
|
|
167
148
|
=== More Info
|
168
149
|
|
169
|
-
* Docs: http://
|
150
|
+
* Docs: http://sshaw.github.io/itunes_store_transporter
|
170
151
|
* Bugs: http://github.com/sshaw/itunes_store_transporter/issues
|
171
152
|
* Source Code: http://github.com/sshaw/itunes_store_transporter
|
172
|
-
* Transporter GUI: http://
|
153
|
+
* iTunes Store Transporter: GUI: http://transportergui.com
|
173
154
|
|
174
155
|
=== Author
|
175
156
|
|
@@ -178,3 +159,7 @@ Skye Shaw [skye.shaw AT gmail.com]
|
|
178
159
|
=== License
|
179
160
|
|
180
161
|
Released under the MIT License: http://www.opensource.org/licenses/MIT
|
162
|
+
|
163
|
+
---
|
164
|
+
|
165
|
+
Made by {ScreenStaring}[http://screenstaring.com]
|
@@ -9,6 +9,7 @@ module ITunes
|
|
9
9
|
VENDOR_ID = [ :vendor_id, "-vendor_id", /\w/ ]
|
10
10
|
APPLE_ID = [ :apple_id, "-apple_id", /\w/ ]
|
11
11
|
SHORTNAME = [ :shortname, "-s", /\w/ ]
|
12
|
+
ITC_PROVIDER= [ :itc_provider, "-itc_provider", /\w/ ]
|
12
13
|
TRANSPORT = [ :transport, "-t", %w|Aspera Signiant DAV| ]
|
13
14
|
SUCCESS = [ :success, "-success", Optout::Dir.exists ]
|
14
15
|
FAILURE = [ :failure, "-failure", Optout::Dir.exists ]
|
@@ -12,6 +12,9 @@ module ITunes
|
|
12
12
|
class Schema < Mode
|
13
13
|
def initialize(*config)
|
14
14
|
super
|
15
|
+
options.on *SHORTNAME
|
16
|
+
options.on *ITC_PROVIDER
|
17
|
+
|
15
18
|
options.on *DESTINATION
|
16
19
|
options.on :type, "-schemaType", /\A(transitional|strict)\z/i, :required => true
|
17
20
|
options.on :version, "-schema", /\w+/i, :required => true
|
@@ -12,6 +12,8 @@ module ITunes
|
|
12
12
|
class Status < Mode
|
13
13
|
def initialize(*config)
|
14
14
|
super
|
15
|
+
options.on *SHORTNAME
|
16
|
+
options.on *ITC_PROVIDER
|
15
17
|
options.on :vendor_id, "-vendor_ids", /\w/, :multiple => true
|
16
18
|
options.on :apple_id, "-apple_ids", /\w/, :multiple => true
|
17
19
|
options.on :format, "-outputFormat", %w[xml]
|
@@ -15,6 +15,8 @@ module ITunes
|
|
15
15
|
options.on *TRANSPORT
|
16
16
|
options.on *SUCCESS
|
17
17
|
options.on *FAILURE
|
18
|
+
options.on *SHORTNAME
|
19
|
+
options.on *ITC_PROVIDER
|
18
20
|
options.on :delete, "-delete", Optout::Boolean
|
19
21
|
options.on :rate, "-k", Integer # Required if TRANSPORT is Aspera or Signiant
|
20
22
|
options.on :streams, "-numStreams", Integer # Only valid if TRANSPORT is Signiant
|
@@ -18,7 +18,8 @@ module ITunes
|
|
18
18
|
#
|
19
19
|
# [:username (String)] Your username
|
20
20
|
# [:password (String)] Your password
|
21
|
-
# [:shortname (String)] Your shortname. Optional, not every iTunes account has one
|
21
|
+
# [:shortname (String)] Your shortname (encoding house user). Optional, not every iTunes account has one
|
22
|
+
# [:itc_provider (String)] Your iTunes Connect user. Optional, not every iTunes account has one
|
22
23
|
# [:path (String)] The path to the +iTMSTransporter+. Optional.
|
23
24
|
# [:print_stdout (Boolean)] Print +iTMSTransporter+'s stdout to your stdout. Defaults to +false+.
|
24
25
|
# [:print_stderr (Boolean)] Print +iTMSTransporter+'s stderr to your stderr. Defaults to +false+.
|
data/spec/command_spec.rb
CHANGED
@@ -19,7 +19,7 @@ shared_examples_for "a transporter option that expects a directory" do |option,
|
|
19
19
|
|
20
20
|
context "when the directory does not exist" do
|
21
21
|
it "raises an OptionError" do
|
22
|
-
|
22
|
+
expect { subject.run(options.merge(option => "__baaaaahd_directory__")) }.to raise_exception(ITunes::Store::Transporter::OptionError, /does not exist/)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -54,7 +54,13 @@ shared_examples_for "a required option" do |option|
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
shared_examples_for "a command that accepts an itc_provider argument" do
|
58
|
+
it_should_behave_like "a transporter option", { :itc_provider => "sshaw" }, "-itc_provider", "sshaw"
|
59
|
+
end
|
60
|
+
|
57
61
|
shared_examples_for "a command that accepts a shortname argument" do
|
62
|
+
it_should_behave_like "a transporter option", { :shortname => "sshaw" }, "-s", "sshaw"
|
63
|
+
|
58
64
|
context "when the shortname's invalid" do
|
59
65
|
it "raises an OptionError" do
|
60
66
|
expect { subject.run(options.merge(:shortname => "+")) }.to raise_exception(ITunes::Store::Transporter::OptionError, /shortname/)
|
@@ -259,7 +265,7 @@ shared_examples_for "a command that requires a package argument" do
|
|
259
265
|
path = Tempfile.new("").path
|
260
266
|
options = create_options(:package => path)
|
261
267
|
# TODO: Optout's error message will probably be changed to something more descriptive, change this when that happens
|
262
|
-
|
268
|
+
expect { subject.run(options) }.to raise_error(ITunes::Store::Transporter::OptionError, /dir/i)
|
263
269
|
end
|
264
270
|
end
|
265
271
|
end
|
@@ -288,6 +294,7 @@ describe ITunes::Store::Transporter::Command::Upload do
|
|
288
294
|
it_behaves_like "a transporter mode"
|
289
295
|
it_behaves_like "a command that requires a package argument"
|
290
296
|
it_behaves_like "a command that accepts a shortname argument"
|
297
|
+
it_behaves_like "a command that accepts an itc_provider argument"
|
291
298
|
|
292
299
|
subject { described_class.new({}) }
|
293
300
|
let(:options) { create_options(:package => create_package, :transport => "Aspera") }
|
@@ -348,6 +355,7 @@ end
|
|
348
355
|
describe ITunes::Store::Transporter::Command::Lookup do
|
349
356
|
it_behaves_like "a transporter mode"
|
350
357
|
it_behaves_like "a command that accepts a shortname argument"
|
358
|
+
it_behaves_like "a command that accepts an itc_provider argument"
|
351
359
|
|
352
360
|
subject { described_class.new({}) }
|
353
361
|
|
@@ -386,7 +394,7 @@ describe ITunes::Store::Transporter::Command::Lookup do
|
|
386
394
|
before { FileUtils.rm_rf(@tmpdir) }
|
387
395
|
|
388
396
|
it "raises a TransporterError" do
|
389
|
-
|
397
|
+
expect { subject.run(options) }.to raise_exception(ITunes::Store::Transporter::TransporterError, /no metadata file/i)
|
390
398
|
end
|
391
399
|
end
|
392
400
|
end
|
@@ -409,6 +417,7 @@ end
|
|
409
417
|
describe ITunes::Store::Transporter::Command::Schema do
|
410
418
|
it_behaves_like "a transporter mode"
|
411
419
|
it_behaves_like "a command that accepts a shortname argument"
|
420
|
+
it_behaves_like "a command that accepts an itc_provider argument"
|
412
421
|
|
413
422
|
subject { described_class.new({}) }
|
414
423
|
let(:options) { create_options(:type => "strict", :version => "film5") }
|
@@ -458,6 +467,8 @@ end
|
|
458
467
|
describe ITunes::Store::Transporter::Command::Status do
|
459
468
|
# Ugh, issue with stubbed print_stdout test, we need XML here :(
|
460
469
|
#it_behaves_like "a transporter mode"
|
470
|
+
it_behaves_like "a command that accepts a shortname argument"
|
471
|
+
it_behaves_like "a command that accepts an itc_provider argument"
|
461
472
|
|
462
473
|
subject { described_class.new({}) }
|
463
474
|
let(:options) { create_options(:vendor_id => 123123) }
|
@@ -578,6 +589,7 @@ describe ITunes::Store::Transporter::Command::Verify do
|
|
578
589
|
it_behaves_like "a transporter mode"
|
579
590
|
it_behaves_like "a command that requires a package argument"
|
580
591
|
it_behaves_like "a command that accepts a shortname argument"
|
592
|
+
it_behaves_like "a command that accepts an itc_provider argument"
|
581
593
|
|
582
594
|
subject { described_class.new({}) }
|
583
595
|
let(:options) { create_options(:package => create_package) }
|
data/spec/errors_spec.rb
CHANGED
@@ -5,48 +5,48 @@ describe ITunes::Store::Transporter::TransporterMessage do
|
|
5
5
|
its(:code) { should == 1 }
|
6
6
|
its(:message) { should == "some message" }
|
7
7
|
|
8
|
-
describe "#to_s" do
|
9
|
-
it "includes the code and message" do
|
8
|
+
describe "#to_s" do
|
9
|
+
it "includes the code and message" do
|
10
10
|
subject.to_s.should == "some message (1)"
|
11
|
-
end
|
11
|
+
end
|
12
12
|
end
|
13
13
|
|
14
|
-
describe "error predicate methods" do
|
15
|
-
method_codes = {
|
16
|
-
:bad_data? => [3000, 3999],
|
14
|
+
describe "error predicate methods" do
|
15
|
+
method_codes = {
|
16
|
+
:bad_data? => [3000, 3999],
|
17
17
|
:invalid_data? => [4000, 4999],
|
18
18
|
:missing_data? => [5000, 5999],
|
19
|
-
:unsupported_feature? => [6000, 6999],
|
20
|
-
:schema_error? => [8000, 8999],
|
21
|
-
:asset_error? => [9000, 9999]
|
19
|
+
:unsupported_feature? => [6000, 6999],
|
20
|
+
:schema_error? => [8000, 8999],
|
21
|
+
:asset_error? => [9000, 9999]
|
22
22
|
}
|
23
23
|
|
24
24
|
method_codes.each do |method, codes|
|
25
25
|
[codes.first, codes.last].each do |code|
|
26
26
|
|
27
|
-
context "code #{code}" do
|
27
|
+
context "code #{code}" do
|
28
28
|
subject { described_class.new("", code) }
|
29
|
-
its(method) { should
|
30
|
-
its(:validation_error?) { should
|
29
|
+
its(method) { should be true }
|
30
|
+
its(:validation_error?) { should be true }
|
31
31
|
|
32
32
|
(method_codes.keys - [method]).each do |other|
|
33
|
-
its(other) { should
|
33
|
+
its(other) { should be false }
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
41
|
-
describe ITunes::Store::Transporter::ExecutionError do
|
40
|
+
|
41
|
+
describe ITunes::Store::Transporter::ExecutionError do
|
42
42
|
subject { described_class.new(messages, 1) }
|
43
43
|
let(:messages) { 2.times.inject([]) { |err, i| err << ITunes::Store::Transporter::TransporterMessage.new("message #{i}", i) } }
|
44
44
|
its(:exitstatus) { should == 1 }
|
45
45
|
its(:errors) { should == messages }
|
46
46
|
|
47
|
-
describe "#to_s" do
|
48
|
-
it "includes each messages and its code" do
|
49
|
-
subject.to_s.should == "message 0 (0), message 1 (1)"
|
47
|
+
describe "#to_s" do
|
48
|
+
it "includes each messages and its code" do
|
49
|
+
subject.to_s.should == "message 0 (0), message 1 (1)"
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
data/spec/output_parser_spec.rb
CHANGED
@@ -54,9 +54,14 @@ describe ITunes::Store::Transporter::OutputParser do
|
|
54
54
|
before(:all) { @parser = described_class.new(fixture("errors_and_warnings.with_error_number")) }
|
55
55
|
|
56
56
|
subject { @parser }
|
57
|
-
its(:warnings) { should be_empty }
|
58
|
-
its(:errors) { should have(2).items }
|
59
57
|
|
58
|
+
it "generates no warnings" do
|
59
|
+
expect(@parser.warnings).to be_empty
|
60
|
+
end
|
61
|
+
|
62
|
+
it "generates two errors" do
|
63
|
+
expect(@parser.errors.size).to eq 2
|
64
|
+
end
|
60
65
|
|
61
66
|
describe "the first error" do
|
62
67
|
subject { @parser.errors.first }
|
@@ -85,8 +90,14 @@ describe ITunes::Store::Transporter::OutputParser do
|
|
85
90
|
before(:all) { @parser = described_class.new(fixture("errors_and_warnings.single_warning")) }
|
86
91
|
|
87
92
|
subject { @parser }
|
88
|
-
|
89
|
-
|
93
|
+
|
94
|
+
it "generates no errors" do
|
95
|
+
expect(@parser.errors).to be_empty
|
96
|
+
end
|
97
|
+
|
98
|
+
it "generates one warning" do
|
99
|
+
expect(@parser.warnings.size).to eq 1
|
100
|
+
end
|
90
101
|
|
91
102
|
describe "the warning" do
|
92
103
|
subject { @parser.warnings.first }
|
data/spec/transporter_spec.rb
CHANGED
@@ -45,7 +45,7 @@ describe ITunes::Store::Transporter::ITMSTransporter do
|
|
45
45
|
describe "#new" do
|
46
46
|
context "when the options are not a Hash or nil" do
|
47
47
|
it "raises an ArgumentError" do
|
48
|
-
|
48
|
+
expect { described_class.new(123) }.to raise_exception(ArgumentError, /must be/)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: itunes_store_transporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: childprocess
|
@@ -72,9 +72,9 @@ dependencies:
|
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: '3'
|
75
|
-
description:
|
76
|
-
|
77
|
-
|
75
|
+
description: |2
|
76
|
+
iTunes::Store::Transporter is a wrapper around Apple's iTMSTransporter program. It allows you to upload packages to the
|
77
|
+
Apple Store, validate them, retrieve status information, lookup metadata, and more!
|
78
78
|
email: skye.shaw@gmail.com
|
79
79
|
executables:
|
80
80
|
- itms
|
@@ -115,16 +115,11 @@ files:
|
|
115
115
|
- spec/spec_helper.rb
|
116
116
|
- spec/transporter_spec.rb
|
117
117
|
- spec/xml_status_spec.rb
|
118
|
-
- spec/xml_status_spec.rb~
|
119
118
|
homepage: http://github.com/sshaw/itunes_store_transporter
|
120
119
|
licenses:
|
121
120
|
- MIT
|
122
121
|
metadata: {}
|
123
|
-
post_install_message:
|
124
|
-
\ ATTENTION WINDOWS USERS !!\n !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n
|
125
|
-
\ \n You must make a small change to the iTMSTransporter.CMD batch file, otherwise
|
126
|
-
\n this library will not function correctly. \n \n For details see: http://github.com/sshaw/itunes_store_transporter#running-on-windows
|
127
|
-
\ \n\n"
|
122
|
+
post_install_message:
|
128
123
|
rdoc_options: []
|
129
124
|
require_paths:
|
130
125
|
- lib
|
@@ -140,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
135
|
version: '0'
|
141
136
|
requirements: []
|
142
137
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.4.
|
138
|
+
rubygems_version: 2.4.5.1
|
144
139
|
signing_key:
|
145
140
|
specification_version: 4
|
146
141
|
summary: Upload and manage your assets in the iTunes Store using the iTunes Store's
|
@@ -158,4 +153,3 @@ test_files:
|
|
158
153
|
- spec/spec_helper.rb
|
159
154
|
- spec/transporter_spec.rb
|
160
155
|
- spec/xml_status_spec.rb
|
161
|
-
- spec/xml_status_spec.rb~
|
data/spec/xml_status_spec.rb~
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
RSpec.describe ITunes::Store::Transporter::XML::Status do
|
4
|
-
context "given a valid XML doc with a single status" do
|
5
|
-
it "returns a Hash with a single status" do
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
context "given a valid XML doc with multiple statuses" do
|
12
|
-
it "returns a Hash with multiple statuses" do
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context "given an XML invalid XML doc" do
|
18
|
-
it "raises a ParseError" do
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|