bucket_client 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.gitlab-ci.yml +70 -0
- data/.idea/bucket_client.iml +105 -0
- data/.idea/encodings.xml +4 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +8 -0
- data/.idea/runConfigurations/Integration_Test.xml +37 -0
- data/.idea/runConfigurations/Unit_Test.xml +37 -0
- data/.rspec +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +114 -0
- data/LICENSE.txt +21 -0
- data/README.md +870 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bucket_client.gemspec +46 -0
- data/integration/aws_blob_spec.rb +134 -0
- data/integration/aws_bucket_spec.rb +145 -0
- data/integration/azure_blob_spec.rb +132 -0
- data/integration/azure_bucket_spec.rb +132 -0
- data/integration/dev_blob_spec.rb +131 -0
- data/integration/dev_bucket_spec.rb +140 -0
- data/integration/do_blob_spec.rb +134 -0
- data/integration/do_bucket_spec.rb +144 -0
- data/integration/gcp_blob_spec.rb +132 -0
- data/integration/gcp_bucket_spec.rb +132 -0
- data/integration/img.jpg +0 -0
- data/lib/bucket_client.rb +66 -0
- data/lib/bucket_client/aws/aws_bucket.rb +85 -0
- data/lib/bucket_client/aws/aws_client.rb +195 -0
- data/lib/bucket_client/aws/aws_http_client.rb +32 -0
- data/lib/bucket_client/aws/aws_policy_factory.rb +26 -0
- data/lib/bucket_client/aws4_request_signer.rb +133 -0
- data/lib/bucket_client/azure/azure_bucket.rb +83 -0
- data/lib/bucket_client/azure/azure_client.rb +197 -0
- data/lib/bucket_client/bucket.rb +388 -0
- data/lib/bucket_client/bucket_operation_exception.rb +8 -0
- data/lib/bucket_client/client.rb +408 -0
- data/lib/bucket_client/dev/local_bucket.rb +84 -0
- data/lib/bucket_client/dev/local_client.rb +148 -0
- data/lib/bucket_client/digital_ocean/digital_ocean_acl_factory.rb +39 -0
- data/lib/bucket_client/digital_ocean/digital_ocean_bucket.rb +81 -0
- data/lib/bucket_client/digital_ocean/digital_ocean_client.rb +275 -0
- data/lib/bucket_client/digital_ocean/digital_ocean_http_client.rb +31 -0
- data/lib/bucket_client/gcp/gcp_bucket.rb +79 -0
- data/lib/bucket_client/gcp/gcp_client.rb +171 -0
- data/lib/bucket_client/operation_result.rb +33 -0
- data/lib/bucket_client/version.rb +3 -0
- metadata +246 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "bucket_client"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "bucket_client/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "bucket_client"
|
7
|
+
spec.version = BucketClient::VERSION
|
8
|
+
spec.authors = ["kirinnee"]
|
9
|
+
spec.email = ["kirinnee97@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{A gem that allows for ruby code to interact with cloud blob storage such as AWS S3, GCP Cloud
|
12
|
+
Storage and Azure Blob Storage}
|
13
|
+
spec.description = %q{Bucket Client is a ruby gem that allows programmers to interact with popular Blob Storage cloud
|
14
|
+
services. This intends to act as a layer of abstraction, much like ORM is to databases.
|
15
|
+
|
16
|
+
With this, you may easily change the blob storage provider or even defer them.
|
17
|
+
|
18
|
+
The supported cloud storage include:
|
19
|
+
- Google Cloud Platform Cloud Storage
|
20
|
+
- Amazon Web Service S3 Bucket
|
21
|
+
- Digital Ocean Spaces
|
22
|
+
- Azure Blob Storage (Microsoft).}
|
23
|
+
spec.homepage = "https://gitlab.com/ruby-gem/bucket_client"
|
24
|
+
spec.license = "MIT"
|
25
|
+
|
26
|
+
# Specify which files should be added to the gem when it is released.
|
27
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
29
|
+
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/})}
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f)}
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_dependency "kirin_http"
|
36
|
+
spec.add_dependency "mimemagic"
|
37
|
+
spec.add_dependency "ox"
|
38
|
+
spec.add_dependency "azure-storage-blob"
|
39
|
+
spec.add_dependency 'addressable', '~> 2.5'
|
40
|
+
spec.add_dependency 'google-cloud-storage', '~> 1.17'
|
41
|
+
|
42
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
43
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
44
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
45
|
+
spec.add_development_dependency "dotenv"
|
46
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "bucket_client/aws/aws_bucket"
|
2
|
+
require "bucket_client/aws/aws_client"
|
3
|
+
require "kirin_http"
|
4
|
+
|
5
|
+
describe BucketClient::AWSBucket do
|
6
|
+
http = KirinHttp::BasicClient.new
|
7
|
+
id = ENV["AWS_ID"]
|
8
|
+
secret = ENV["AWS_SECRET"]
|
9
|
+
region = ENV["AWS_REGION"]
|
10
|
+
client = BucketClient::AWSClient.new(http, id, secret, region)
|
11
|
+
key = "kirin.bucket.client.bucket.integration"
|
12
|
+
file = "img.jpg"
|
13
|
+
bin = IO.binread "./integration/#{file}"
|
14
|
+
before(:all) {client.put_bucket key}
|
15
|
+
after(:all) {
|
16
|
+
bucket = client.get_bucket key
|
17
|
+
bucket.delete_blob_if_exist file
|
18
|
+
client.delete_bucket_if_exist key
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
describe 'Blob Behaviour' do
|
23
|
+
bucket = client.get_bucket! key
|
24
|
+
describe 'exist_blob' do
|
25
|
+
it 'should return false as blob does not exist' do
|
26
|
+
expect(bucket.exist_blob file).to be_falsey
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'delete_blob_if_exist' do
|
31
|
+
it 'should succeed' do
|
32
|
+
result = bucket.delete_blob_if_exist file
|
33
|
+
expect(result.code).to eq 204
|
34
|
+
expect(result.success).to be_truthy
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'delete_blob' do
|
39
|
+
it 'should fail as blob does not exist' do
|
40
|
+
result = bucket.delete_blob file
|
41
|
+
expect(result.success).to be_falsey
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'update_blob' do
|
46
|
+
it 'should fail as blob does not exist' do
|
47
|
+
result = bucket.update_blob bin, file
|
48
|
+
expect(result.success).to be_falsey
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'get_blob' do
|
53
|
+
it 'should fail as blob does not exist' do
|
54
|
+
result = bucket.get_blob file
|
55
|
+
expect(result.success).to be_falsey
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "put_blob" do
|
60
|
+
it 'should succeed' do
|
61
|
+
result = bucket.put_blob bin, file
|
62
|
+
expect(result).to be_truthy
|
63
|
+
expect(result.value).to eq "https://s3-#{region}.amazonaws.com/#{key}/#{file}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'exist_blob' do
|
68
|
+
it 'should exist' do
|
69
|
+
expect(bucket.exist_blob file).to be_truthy
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "create_blob" do
|
74
|
+
it 'should fail as blob already exist' do
|
75
|
+
result = bucket.create_blob bin, file
|
76
|
+
expect(result.success).to be_falsey
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "get_blob" do
|
81
|
+
it 'should obtain the binary of the blob' do
|
82
|
+
result = bucket.get_blob file
|
83
|
+
expect(result.success).to be_truthy
|
84
|
+
expect(result.value).to eq bin
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "delete_blob" do
|
89
|
+
it 'should pass as blob exist' do
|
90
|
+
result = bucket.delete_blob file
|
91
|
+
expect(result.success).to be_truthy
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "create_blob" do
|
96
|
+
it 'should work as blob does not exist' do
|
97
|
+
result = bucket.create_blob bin, file
|
98
|
+
expect(result.success).to be_truthy
|
99
|
+
expect(result.value).to eq "https://s3-#{region}.amazonaws.com/#{key}/#{file}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "update_blob" do
|
104
|
+
it 'should work as blob exist' do
|
105
|
+
result = bucket.update_blob bin, file
|
106
|
+
expect(result.success).to be_truthy
|
107
|
+
expect(result.value).to eq "https://s3-#{region}.amazonaws.com/#{key}/#{file}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "put_blob" do
|
112
|
+
it 'should work as blob exist' do
|
113
|
+
result = bucket.put_blob bin, file
|
114
|
+
expect(result.success).to be_truthy
|
115
|
+
expect(result.value).to eq "https://s3-#{region}.amazonaws.com/#{key}/#{file}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "delete_blob_if_exist" do
|
120
|
+
it 'should delete blob' do
|
121
|
+
result = bucket.delete_blob_if_exist file
|
122
|
+
expect(result.success).to be_truthy
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "exist_blob" do
|
127
|
+
it 'should no longer exist' do
|
128
|
+
expect(bucket.exist_blob file).to be_falsey
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "bucket_client/aws/aws_bucket"
|
2
|
+
require "bucket_client/aws/aws_client"
|
3
|
+
require "kirin_http"
|
4
|
+
|
5
|
+
describe BucketClient::AWSClient do
|
6
|
+
|
7
|
+
http = KirinHttp::BasicClient.new
|
8
|
+
id = ENV["AWS_ID"]
|
9
|
+
secret = ENV["AWS_SECRET"]
|
10
|
+
region = ENV["AWS_REGION"]
|
11
|
+
client = BucketClient::AWSClient.new(http, id, secret, region)
|
12
|
+
key = "kirin.bucket.client.client.integration"
|
13
|
+
|
14
|
+
|
15
|
+
before(:all) {client.delete_bucket_if_exist key}
|
16
|
+
after(:all) {client.delete_bucket_if_exist key}
|
17
|
+
|
18
|
+
describe 'exist_bucket' do
|
19
|
+
it 'should return false' do
|
20
|
+
expect(client.exist_bucket key).to be_falsey
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'delete_bucket_if_exist' do
|
25
|
+
it 'should be successful' do
|
26
|
+
result = client.delete_bucket_if_exist key
|
27
|
+
expect(result.code).to eq 204
|
28
|
+
expect(result.success).to be_truthy
|
29
|
+
expect(result.message).to eq "Bucket already deleted"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'delete_bucket' do
|
34
|
+
it 'should fail as bucket does not exist' do
|
35
|
+
result = client.delete_bucket key
|
36
|
+
expect(result.code).to eq 404
|
37
|
+
expect(result.success).to be_falsey
|
38
|
+
expect(result.value).to be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "set_read_policy" do
|
43
|
+
it 'should fail as bucket does not exist' do
|
44
|
+
result = client.set_read_policy key, :public
|
45
|
+
expect(result.code).to eq 404
|
46
|
+
expect(result.success).to be_falsey
|
47
|
+
expect(result.value).to be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "set_get_cors" do
|
52
|
+
it 'should fail as bucket does notexist' do
|
53
|
+
result = client.set_get_cors key, ["*"]
|
54
|
+
expect(result.code).to eq 404
|
55
|
+
expect(result.success).to be_falsey
|
56
|
+
expect(result.value).to be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "get_bucket!" do
|
61
|
+
it 'should generate unsafe bucket with correct key' do
|
62
|
+
bucket = client.get_bucket! key
|
63
|
+
expect(bucket.key).to eq key
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "put_bucket" do
|
68
|
+
it 'should create new bucket' do
|
69
|
+
result = client.put_bucket key
|
70
|
+
expect(result.code).to eq 200
|
71
|
+
expect(result.success).to be_truthy
|
72
|
+
expect(result.value.key).to eq key
|
73
|
+
sleep(10)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'create_bucket' do
|
78
|
+
it 'should fail creating a new bucket because it already exist' do
|
79
|
+
|
80
|
+
expect(client.exist_bucket key).to be_truthy
|
81
|
+
result = client.create_bucket key
|
82
|
+
expect(result.code).to eq 409
|
83
|
+
expect(result.success).to be_falsey
|
84
|
+
expect(result.value).to be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'delete_bucket' do
|
89
|
+
it 'should succeed deleting a new bucket' do
|
90
|
+
result = client.delete_bucket key
|
91
|
+
expect(result.code).to eq 204
|
92
|
+
expect(result.success).to be_truthy
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "create_bucket" do
|
97
|
+
it 'should succeed at creating a new bucket' do
|
98
|
+
result = client.create_bucket key
|
99
|
+
expect(result.code).to eq 200
|
100
|
+
expect(result.success).to be_truthy
|
101
|
+
expect(result.value.key).to eq key
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'put_bucket' do
|
106
|
+
it 'should not fail even if bucket already exist' do
|
107
|
+
result = client.put_bucket key
|
108
|
+
expect(result.code).to eq 200
|
109
|
+
expect(result.success).to be_truthy
|
110
|
+
expect(result.value.key).to eq key
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "set_read_policy" do
|
115
|
+
it 'should fail if access is neither :public or :private' do
|
116
|
+
expect {client.set_read_policy key, :protected}.to raise_error(ArgumentError, "Read Policy not accepted")
|
117
|
+
end
|
118
|
+
it 'should succeed' do
|
119
|
+
result = client.set_read_policy key, :public
|
120
|
+
expect(result.code).to eq 204
|
121
|
+
expect(result.success).to be_truthy
|
122
|
+
expect(result.value).to be_nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "set_get_cors" do
|
127
|
+
it 'should should succeed' do
|
128
|
+
result = client.set_get_cors key, ["*"]
|
129
|
+
expect(result.code).to eq 200
|
130
|
+
expect(result.success).to be_truthy
|
131
|
+
expect(result.value).to be_nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "delete_bucket_if_exist" do
|
136
|
+
it 'should remove existing bucket' do
|
137
|
+
result = client.delete_bucket_if_exist key
|
138
|
+
expect(result.code).to eq 204
|
139
|
+
expect(result.success).to be_truthy
|
140
|
+
expect(result.value).to be_nil
|
141
|
+
expect(client.exist_bucket key).to be_falsey
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "bucket_client/azure/azure_bucket"
|
2
|
+
require "bucket_client/azure/azure_client"
|
3
|
+
|
4
|
+
describe BucketClient::AzureBucket do
|
5
|
+
id = ENV["AZURE_ACC_ID"]
|
6
|
+
secret = ENV["AZURE_KEY"]
|
7
|
+
client = BucketClient::AzureClient.new(id, secret)
|
8
|
+
key = "kirin-bucket-client-bucket-integration"
|
9
|
+
file = "img.jpg"
|
10
|
+
bin = IO.binread "./integration/#{file}"
|
11
|
+
|
12
|
+
before(:all) {client.put_bucket key}
|
13
|
+
after(:all) {
|
14
|
+
bucket = client.get_bucket key
|
15
|
+
bucket.delete_blob_if_exist file
|
16
|
+
client.delete_bucket_if_exist key
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
describe 'Blob Behaviour' do
|
21
|
+
bucket = client.get_bucket! key
|
22
|
+
describe 'exist_blob' do
|
23
|
+
it 'should return false as blob does not exist' do
|
24
|
+
expect(bucket.exist_blob file).to be_falsey
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'delete_blob_if_exist' do
|
29
|
+
it 'should succeed' do
|
30
|
+
result = bucket.delete_blob_if_exist file
|
31
|
+
expect(result.code).to eq 204
|
32
|
+
expect(result.success).to be_truthy
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'delete_blob' do
|
37
|
+
it 'should fail as blob does not exist' do
|
38
|
+
result = bucket.delete_blob file
|
39
|
+
expect(result.success).to be_falsey
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'update_blob' do
|
44
|
+
it 'should fail as blob does not exist' do
|
45
|
+
result = bucket.update_blob bin, file
|
46
|
+
expect(result.success).to be_falsey
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'get_blob' do
|
51
|
+
it 'should fail as blob does not exist' do
|
52
|
+
result = bucket.get_blob file
|
53
|
+
expect(result.success).to be_falsey
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "put_blob" do
|
58
|
+
it 'should succeed' do
|
59
|
+
result = bucket.put_blob bin, file
|
60
|
+
expect(result.success).to be_truthy
|
61
|
+
expect(result.value).to eq "https://#{id}.blob.core.windows.net/#{key}/#{file}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'exist_blob' do
|
66
|
+
it 'should exist' do
|
67
|
+
expect(bucket.exist_blob file).to be_truthy
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "create_blob" do
|
72
|
+
it 'should fail as blob already exist' do
|
73
|
+
result = bucket.create_blob bin, file
|
74
|
+
expect(result.success).to be_falsey
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "get_blob" do
|
79
|
+
it 'should obtain the binary of the blob' do
|
80
|
+
result = bucket.get_blob file
|
81
|
+
expect(result.success).to be_truthy
|
82
|
+
expect(result.value).to eq bin
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "delete_blob" do
|
87
|
+
it 'should pass as blob exist' do
|
88
|
+
result = bucket.delete_blob file
|
89
|
+
expect(result.success).to be_truthy
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "create_blob" do
|
94
|
+
it 'should work as blob does not exist' do
|
95
|
+
result = bucket.create_blob bin, file
|
96
|
+
expect(result.success).to be_truthy
|
97
|
+
expect(result.value).to eq "https://#{id}.blob.core.windows.net/#{key}/#{file}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "update_blob" do
|
102
|
+
it 'should work as blob exist' do
|
103
|
+
result = bucket.update_blob bin, file
|
104
|
+
expect(result.success).to be_truthy
|
105
|
+
expect(result.value).to eq "https://#{id}.blob.core.windows.net/#{key}/#{file}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "put_blob" do
|
110
|
+
it 'should work as blob exist' do
|
111
|
+
result = bucket.put_blob bin, file
|
112
|
+
expect(result.success).to be_truthy
|
113
|
+
expect(result.value).to eq "https://#{id}.blob.core.windows.net/#{key}/#{file}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "delete_blob_if_exist" do
|
118
|
+
it 'should delete blob' do
|
119
|
+
result = bucket.delete_blob_if_exist file
|
120
|
+
expect(result.success).to be_truthy
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "exist_blob" do
|
125
|
+
it 'should no longer exist' do
|
126
|
+
expect(bucket.exist_blob file).to be_falsey
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|