one_sky 1.0.0 → 2.0.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.
- data/.rspec +2 -0
- data/Gemfile.lock +13 -13
- data/README.rdoc +51 -7
- data/Rakefile +17 -1
- data/lib/one_sky.rb +8 -9
- data/lib/one_sky/client.rb +80 -0
- data/lib/one_sky/platform.rb +36 -0
- data/lib/one_sky/project.rb +22 -114
- data/lib/one_sky/translation.rb +129 -0
- data/lib/one_sky/utility.rb +38 -0
- data/lib/one_sky/version.rb +1 -1
- data/one_sky.gemspec +4 -3
- data/spec/client_spec.rb +172 -0
- data/spec/files/example.po +2 -0
- data/spec/files/example.yml +5 -0
- data/spec/live_spec.rb +187 -0
- data/spec/platform_spec.rb +101 -0
- data/spec/project_spec.rb +67 -48
- data/spec/{helpers.rb → spec_helper.rb} +2 -2
- data/spec/translation_spec.rb +124 -0
- data/spec/utility_spec.rb +98 -0
- metadata +45 -21
- data/spec/data/en-us.po +0 -21
- data/spec/input_spec.rb +0 -64
- data/spec/output_spec.rb +0 -35
@@ -0,0 +1,129 @@
|
|
1
|
+
module OneSky
|
2
|
+
|
3
|
+
# Implements the Translation I/O API for a given :platform_id
|
4
|
+
class Translation
|
5
|
+
|
6
|
+
attr_reader :platform_id, :client
|
7
|
+
|
8
|
+
# Provide the id of the platform, together with an instance of OneSky::Client.
|
9
|
+
def initialize(platform_id, client)
|
10
|
+
@platform_id = platform_id
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add new strings to be translated.
|
15
|
+
# expects an array of strings, or an array of hashes [{:string_key => k, :string => v}, ...]
|
16
|
+
def input_strings(strings)
|
17
|
+
post("string/input", :input => format_input_strings(strings))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add new strings to be translated.
|
21
|
+
# expects a string, or a hash of {:string_key => k, :string => v}
|
22
|
+
def input_string(string)
|
23
|
+
input_strings([string])
|
24
|
+
end
|
25
|
+
|
26
|
+
# Add new strings to be translated.
|
27
|
+
# expects a hash of {"string_key1" => "String 1", "string_key2" => "String 2"}
|
28
|
+
def input_phrases(phrases)
|
29
|
+
strings = phrases.map do |string_key, string|
|
30
|
+
{:string_key => string_key, :string => string}
|
31
|
+
end
|
32
|
+
input_strings(strings)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add translation to a string.
|
36
|
+
def translate(string_key, locale, translation)
|
37
|
+
post("string/translate", :"string-key" => string_key, :locale => locale, :translation => translation)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the strings with translations.
|
41
|
+
def output
|
42
|
+
get_output
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the strings for a particular locale.
|
46
|
+
def output_for_locale(locale)
|
47
|
+
get_output(locale)
|
48
|
+
end
|
49
|
+
|
50
|
+
YAML_FORMAT = "RUBY_YAML".freeze
|
51
|
+
PO_FORMAT = "GNU_PO".freeze
|
52
|
+
|
53
|
+
# I don't believe these work right now.
|
54
|
+
|
55
|
+
# Upload a string file to add new strings. In RUBY_YAML format.
|
56
|
+
def upload_yaml(file)
|
57
|
+
upload_file(file, YAML_FORMAT)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Upload a string file to add new strings. In GNU_PO format.
|
61
|
+
def upload_po(file)
|
62
|
+
upload_file(file, PO_FORMAT)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Download strings and translations as string file. In RUBY_YAML format.
|
66
|
+
def download_yaml(locale)
|
67
|
+
download_file(locale, YAML_FORMAT)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Download strings and translations as string file. In GNU_PO format.
|
71
|
+
def download_po(locale)
|
72
|
+
download_file(locale, PO_FORMAT)
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
# Get the strings with translations.
|
78
|
+
def get_output(locale=nil)
|
79
|
+
options = {}
|
80
|
+
options[:locale] = locale if locale
|
81
|
+
|
82
|
+
get("string/output", options)["translation"]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Upload a string file to add new strings.
|
86
|
+
def upload_file(file, format)
|
87
|
+
post("string/upload", :file => file, :format => format)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Download strings and translations as string file.
|
91
|
+
def download_file(locale, format)
|
92
|
+
get("string/download", :locale => locale, :format => format)
|
93
|
+
end
|
94
|
+
|
95
|
+
def get(path, params={})
|
96
|
+
client.get(path, params.merge(:"platform-id" => platform_id))
|
97
|
+
end
|
98
|
+
|
99
|
+
def post(path, params={})
|
100
|
+
client.post(path, params.merge(:"platform-id" => platform_id))
|
101
|
+
end
|
102
|
+
|
103
|
+
def format_input_strings(strings)
|
104
|
+
JSON.dump(strings.map{|string| format_input_string(string)})
|
105
|
+
end
|
106
|
+
|
107
|
+
def format_input_string(string)
|
108
|
+
case string
|
109
|
+
when String
|
110
|
+
{:string => string}
|
111
|
+
when Hash
|
112
|
+
dashify_string_hash(string)
|
113
|
+
else
|
114
|
+
raise "input string must either be a string, or a hash"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# convert to "string-key" not "string_key"
|
119
|
+
def dashify_string_hash(string_hash)
|
120
|
+
output = Hash.new
|
121
|
+
string_hash.each do |key, value|
|
122
|
+
dashed = key.to_s.gsub("_", "-").to_sym
|
123
|
+
output[dashed] = value
|
124
|
+
end
|
125
|
+
output
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module OneSky
|
2
|
+
|
3
|
+
# Implements the Utility API
|
4
|
+
class Utility
|
5
|
+
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get available locale list.
|
13
|
+
def locales
|
14
|
+
get("locales")["locales"]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get available platform type list.
|
18
|
+
def platform_types
|
19
|
+
get("platform-types")["types"]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get the Single-Sign-On link for logging in.
|
23
|
+
def get_sso_link(unique_id, name)
|
24
|
+
post("sso/get-link", {:"unique-id" => unique_id, :name => name})["url"]
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def get(path)
|
30
|
+
client.get(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def post(path, params)
|
34
|
+
client.post(path, params)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/one_sky/version.rb
CHANGED
data/one_sky.gemspec
CHANGED
@@ -14,11 +14,12 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.rubyforge_project = "one_sky"
|
16
16
|
|
17
|
-
s.add_dependency "json", ">= 1.4.
|
18
|
-
s.add_dependency "rest-client", "
|
17
|
+
s.add_dependency "json", ">= 1.4.0"
|
18
|
+
s.add_dependency "rest-client", ">= 1.4.0"
|
19
19
|
|
20
20
|
s.add_development_dependency "rspec", "~> 2.2.0"
|
21
|
-
s.add_development_dependency "bundler", "
|
21
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
22
|
+
s.add_development_dependency "time_freeze"
|
22
23
|
|
23
24
|
s.files = `git ls-files`.split("\n")
|
24
25
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OneSky::Client do
|
4
|
+
|
5
|
+
let(:api_key) { "apikey" }
|
6
|
+
let(:secret) { "secret" }
|
7
|
+
|
8
|
+
let(:client) { OneSky::Client.new(api_key, secret) }
|
9
|
+
|
10
|
+
let(:fake_response) { mock("some response", :headers => {:content_type => "text/plain"}, :body => "response") }
|
11
|
+
let(:fake_json) { mock("json response", :headers => {:content_type => "json"}, :body => %|{"some": "json"}|) }
|
12
|
+
|
13
|
+
describe "proxies" do
|
14
|
+
describe "utility" do
|
15
|
+
it "returns a OneSky::Utility instance" do
|
16
|
+
client.utility.should be_an_instance_of OneSky::Utility
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets the client to itself" do
|
20
|
+
client.utility.client.should == client
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "project" do
|
25
|
+
it "returns a OneSky::Project instance" do
|
26
|
+
client.project("some-project").should be_an_instance_of OneSky::Project
|
27
|
+
end
|
28
|
+
|
29
|
+
it "sets the client to itself" do
|
30
|
+
client.project("some-project").client.should == client
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sets the provided project name" do
|
34
|
+
client.project("some-project").project_name.should == "some-project"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "get" do
|
41
|
+
|
42
|
+
it "GETs the given path" do
|
43
|
+
RestClient.should respond_to(:post)
|
44
|
+
RestClient.should_receive(:get).with("https://api.oneskyapp.com/2/some_path/to/something", kind_of(Hash)).and_return(fake_response)
|
45
|
+
client.get("some_path/to/something")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "GETs the given path - when given a leading /" do
|
49
|
+
RestClient.should_receive(:get).with("https://api.oneskyapp.com/2/some_path/to/something", kind_of(Hash)).and_return(fake_response)
|
50
|
+
client.get("/some_path/to/something")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "passes in the api key, timestamp, and a hash of the secret" do
|
54
|
+
Time.freeze! do
|
55
|
+
timestamp = Time.now.to_i.to_s
|
56
|
+
RestClient.should_receive(:get).with(
|
57
|
+
kind_of(String),
|
58
|
+
{:params => {
|
59
|
+
:"api-key" => api_key,
|
60
|
+
:timestamp => timestamp,
|
61
|
+
:"dev-hash" => Digest::MD5.hexdigest(timestamp + secret)
|
62
|
+
},
|
63
|
+
:content_type => "text/plain; charset=UTF-8"}
|
64
|
+
).and_return(fake_response)
|
65
|
+
client.get("some_path/to/something")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "adds any provided params" do
|
70
|
+
Time.freeze! do
|
71
|
+
timestamp = Time.now.to_i.to_s
|
72
|
+
RestClient.should_receive(:get).with(
|
73
|
+
kind_of(String),
|
74
|
+
{:params => {
|
75
|
+
:"api-key" => api_key,
|
76
|
+
:timestamp => timestamp,
|
77
|
+
:"dev-hash" => Digest::MD5.hexdigest(timestamp + secret),
|
78
|
+
:some => "other param"
|
79
|
+
},
|
80
|
+
:content_type => "text/plain; charset=UTF-8"}
|
81
|
+
).and_return(fake_response)
|
82
|
+
client.get("some_path/to/something", :some => "other param")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "with a json content_type" do
|
87
|
+
it "parses the json" do
|
88
|
+
RestClient.should_receive(:get).and_return(fake_json)
|
89
|
+
client.get("some_path").should == {"some" => "json"}
|
90
|
+
end
|
91
|
+
|
92
|
+
it "raises an error if the response: is not OK" do
|
93
|
+
fake_json.should_receive(:body).and_return(%|{"response": "bad"}|)
|
94
|
+
RestClient.should_receive(:get).and_return(fake_json)
|
95
|
+
expect {
|
96
|
+
client.get("some_path")
|
97
|
+
}.to raise_error(OneSky::ApiError)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "without a non-json content_type" do
|
102
|
+
it "returns the body" do
|
103
|
+
RestClient.should_receive(:get).and_return(fake_response)
|
104
|
+
client.get("some_path").should == fake_response.body
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "post" do
|
110
|
+
|
111
|
+
it "POSTSs the given path" do
|
112
|
+
RestClient.should respond_to(:post)
|
113
|
+
RestClient.should_receive(:post).with("https://api.oneskyapp.com/2/some_path/to/something", kind_of(Hash)).and_return(fake_response)
|
114
|
+
client.post("some_path/to/something")
|
115
|
+
end
|
116
|
+
|
117
|
+
it "passes in the api key, timestamp, and a hash of the secret" do
|
118
|
+
Time.freeze! do
|
119
|
+
timestamp = Time.now.to_i.to_s
|
120
|
+
RestClient.should_receive(:post).with(
|
121
|
+
kind_of(String),
|
122
|
+
{
|
123
|
+
:"api-key" => api_key,
|
124
|
+
:timestamp => timestamp,
|
125
|
+
:"dev-hash" => Digest::MD5.hexdigest(timestamp + secret),
|
126
|
+
:content_type => "text/plain; charset=UTF-8"
|
127
|
+
}
|
128
|
+
).and_return(fake_response)
|
129
|
+
client.post("some_path/to/something")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "adds any provided params" do
|
134
|
+
Time.freeze! do
|
135
|
+
timestamp = Time.now.to_i.to_s
|
136
|
+
RestClient.should_receive(:post).with(
|
137
|
+
kind_of(String),
|
138
|
+
{
|
139
|
+
:"api-key" => api_key,
|
140
|
+
:timestamp => timestamp,
|
141
|
+
:"dev-hash" => Digest::MD5.hexdigest(timestamp + secret),
|
142
|
+
:some => "other param",
|
143
|
+
:content_type => "text/plain; charset=UTF-8"
|
144
|
+
}
|
145
|
+
).and_return(fake_response)
|
146
|
+
client.post("some_path/to/something", :some => "other param")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with a json content_type" do
|
151
|
+
it "parses the json" do
|
152
|
+
RestClient.should_receive(:post).and_return(fake_json)
|
153
|
+
client.post("some_path").should == {"some" => "json"}
|
154
|
+
end
|
155
|
+
|
156
|
+
it "raises an error if the response: is not OK" do
|
157
|
+
fake_json.should_receive(:body).and_return(%|{"response": "bad"}|)
|
158
|
+
RestClient.should_receive(:post).and_return(fake_json)
|
159
|
+
expect {
|
160
|
+
client.post("some_path")
|
161
|
+
}.to raise_error(OneSky::ApiError)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "without a non-json content_type" do
|
166
|
+
it "returns the body" do
|
167
|
+
RestClient.should_receive(:post).and_return(fake_response)
|
168
|
+
client.post("some_path").should == fake_response.body
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/spec/live_spec.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# this spec runs through live api calls
|
4
|
+
# and requires a real api key for the account
|
5
|
+
#
|
6
|
+
# http://ruby-gem-tests.oneskyapp.com/
|
7
|
+
#
|
8
|
+
# before running this test we need to do
|
9
|
+
#
|
10
|
+
# export ONESKY_API_KEY="the key"
|
11
|
+
# export ONESKY_API_SECRET="the secret"
|
12
|
+
#
|
13
|
+
# the rake spec task skips these test
|
14
|
+
# you need to run
|
15
|
+
#
|
16
|
+
# rake spec:live
|
17
|
+
#
|
18
|
+
require 'spec_helper'
|
19
|
+
require 'yaml'
|
20
|
+
|
21
|
+
describe OneSky, :live => true do
|
22
|
+
|
23
|
+
let(:onesky_api_key) { ENV["ONESKY_API_KEY"] }
|
24
|
+
let(:onesky_api_secret) { ENV["ONESKY_API_SECRET"] }
|
25
|
+
let(:project_name) { "api-test" }
|
26
|
+
let(:platform_id) { 840 }
|
27
|
+
let(:platform_code) { "website" }
|
28
|
+
|
29
|
+
let(:english_locale) { "en_US" }
|
30
|
+
let(:chinese_locale) { "zh_CN" }
|
31
|
+
|
32
|
+
let(:client) do
|
33
|
+
client = OneSky::Client.new(onesky_api_key, onesky_api_secret)
|
34
|
+
end
|
35
|
+
|
36
|
+
context "utility apis" do
|
37
|
+
let(:utility) do
|
38
|
+
client.utility
|
39
|
+
end
|
40
|
+
|
41
|
+
it "locales" do
|
42
|
+
locales = utility.locales
|
43
|
+
locales.should be_an_instance_of Array
|
44
|
+
locales.should_not be_empty
|
45
|
+
en_us = locales.detect{|l| l["locale"] == english_locale }
|
46
|
+
en_us["name"]["eng"].should == "English (US)"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "platform_types" do
|
50
|
+
types = utility.platform_types
|
51
|
+
types.should be_an_instance_of Array
|
52
|
+
types.should_not be_empty
|
53
|
+
website = types.detect{|t| t["code"] == platform_code }
|
54
|
+
website.should == {"name" => "Website", "code" => platform_code}
|
55
|
+
end
|
56
|
+
|
57
|
+
it "get_sso_link" do
|
58
|
+
link = utility.get_sso_link(123, "fred")
|
59
|
+
link.should be_an_instance_of String
|
60
|
+
link.should match(".oneskyapp.com/")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "project" do
|
65
|
+
let(:project) do
|
66
|
+
client.project(project_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "details" do
|
70
|
+
details = project.details
|
71
|
+
details.should be_an_instance_of Hash
|
72
|
+
details["name"].should == project_name
|
73
|
+
end
|
74
|
+
|
75
|
+
it "platforms" do
|
76
|
+
platforms = project.platforms
|
77
|
+
platforms.should be_an_instance_of Array
|
78
|
+
platforms.should_not be_empty
|
79
|
+
|
80
|
+
expected = platforms.detect{|p| p["id"]== platform_id}
|
81
|
+
expected.should be_an_instance_of Hash
|
82
|
+
expected["code"].should == platform_code
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "platform" do
|
87
|
+
let(:platform) do
|
88
|
+
client.project(project_name).platform(platform_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "details" do
|
92
|
+
details = platform.details
|
93
|
+
details.should be_an_instance_of Hash
|
94
|
+
details["project"].should == project_name
|
95
|
+
details["type"]["code"].should == platform_code
|
96
|
+
end
|
97
|
+
|
98
|
+
it "locales" do
|
99
|
+
locales = platform.locales
|
100
|
+
locales.should be_an_instance_of Array
|
101
|
+
locales.should_not be_empty
|
102
|
+
en_us = locales.detect{|l| l["locale"] == english_locale }
|
103
|
+
en_us["name"]["eng"].should == "English (US)"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "translation" do
|
108
|
+
let(:translation) do
|
109
|
+
client.project(project_name).platform(platform_id).translation
|
110
|
+
end
|
111
|
+
|
112
|
+
it "input_phrases" do
|
113
|
+
translation.input_phrases("test1" => "Test 1", "test2" => "Test 2")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "input_strings - as strings" do
|
117
|
+
translation.input_strings(["test1", "test2"])
|
118
|
+
end
|
119
|
+
|
120
|
+
it "input_strings - as hashes" do
|
121
|
+
translation.input_strings([{:string => "Test 3", :string_key => "test3"}, {:string => "Test 4", :string_key => "test4"}])
|
122
|
+
end
|
123
|
+
|
124
|
+
it "input_string - as string" do
|
125
|
+
translation.input_string("test5")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "input_string - as hash" do
|
129
|
+
translation.input_string({:string => "Test 6", :string_key => "test6"})
|
130
|
+
end
|
131
|
+
|
132
|
+
it "translate" do
|
133
|
+
translation.translate("test1", chinese_locale, "测试一")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "output" do
|
137
|
+
output = translation.output
|
138
|
+
output.should be_an_instance_of Hash
|
139
|
+
|
140
|
+
output["Default"].should have_key(english_locale)
|
141
|
+
output["Default"].should have_key(chinese_locale)
|
142
|
+
|
143
|
+
output["Default"][english_locale].should have_key("test1")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "output_for_locale" do
|
147
|
+
output = translation.output_for_locale(chinese_locale)
|
148
|
+
output.should be_an_instance_of Hash
|
149
|
+
|
150
|
+
output["Default"].keys.should == [chinese_locale]
|
151
|
+
output["Default"][chinese_locale].should have_key("test1")
|
152
|
+
end
|
153
|
+
|
154
|
+
def file(filename)
|
155
|
+
path = File.join(File.dirname(__FILE__), "files", filename)
|
156
|
+
File.open(File.expand_path(path))
|
157
|
+
end
|
158
|
+
|
159
|
+
it "upload_yaml" do
|
160
|
+
translation.upload_yaml(file("example.yml"))
|
161
|
+
end
|
162
|
+
|
163
|
+
it "upload_po" do
|
164
|
+
translation.upload_po(file("example.po"))
|
165
|
+
end
|
166
|
+
|
167
|
+
it "download_yaml" do
|
168
|
+
yaml = translation.download_yaml(chinese_locale)
|
169
|
+
yaml.should be_an_instance_of String
|
170
|
+
|
171
|
+
hash = YAML.load(yaml)
|
172
|
+
hash.should be_an_instance_of Hash
|
173
|
+
|
174
|
+
hash[chinese_locale].should have_key("test1")
|
175
|
+
end
|
176
|
+
|
177
|
+
it "download_po" do
|
178
|
+
po = translation.download_po(chinese_locale)
|
179
|
+
po.should be_an_instance_of String
|
180
|
+
|
181
|
+
# some way of verifying it looks like a po
|
182
|
+
po.should match(%(msgid "test1"\nmsgstr "))
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
end
|