search-kit 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/config/locales/en.yml +112 -3
- data/lib/search_kit.rb +5 -6
- data/lib/search_kit/cli.rb +45 -10
- data/lib/search_kit/cli/documents.rb +75 -0
- data/lib/search_kit/cli/events.rb +88 -0
- data/lib/search_kit/cli/indices.rb +96 -0
- data/lib/search_kit/cli/search.rb +60 -0
- data/lib/search_kit/cli/subscribers.rb +45 -0
- data/lib/search_kit/clients.rb +12 -0
- data/lib/search_kit/clients/documents.rb +71 -0
- data/lib/search_kit/clients/events.rb +77 -0
- data/lib/search_kit/clients/indices.rb +70 -0
- data/lib/search_kit/clients/keys.rb +78 -0
- data/lib/search_kit/clients/populate.rb +72 -0
- data/lib/search_kit/clients/scaffold.rb +36 -0
- data/lib/search_kit/clients/search.rb +33 -0
- data/lib/search_kit/clients/subscribers.rb +54 -0
- data/lib/search_kit/configuration.rb +0 -1
- data/lib/search_kit/errors.rb +4 -1
- data/lib/search_kit/messages.rb +41 -0
- data/lib/search_kit/messages/messaging.rb +64 -0
- data/lib/search_kit/models.rb +14 -0
- data/lib/search_kit/models/document.rb +30 -0
- data/lib/search_kit/models/documents.rb +31 -0
- data/lib/search_kit/models/event.rb +18 -0
- data/lib/search_kit/models/events.rb +31 -0
- data/lib/search_kit/models/key.rb +26 -0
- data/lib/search_kit/models/keys.rb +38 -0
- data/lib/search_kit/models/search.rb +17 -0
- data/lib/search_kit/models/subscriber.rb +26 -0
- data/lib/search_kit/polling.rb +30 -0
- data/lib/search_kit/polling/process.rb +40 -0
- data/lib/search_kit/thor.rb +12 -0
- data/lib/search_kit/version.rb +1 -1
- data/search-kit.gemspec +2 -0
- metadata +58 -20
- data/lib/search_kit/documents.rb +0 -59
- data/lib/search_kit/documents/cli.rb +0 -70
- data/lib/search_kit/events.rb +0 -59
- data/lib/search_kit/events/cli.rb +0 -52
- data/lib/search_kit/events/cli/complete.rb +0 -34
- data/lib/search_kit/events/cli/list.rb +0 -48
- data/lib/search_kit/events/cli/pending.rb +0 -48
- data/lib/search_kit/events/cli/publish.rb +0 -42
- data/lib/search_kit/events/cli/status.rb +0 -43
- data/lib/search_kit/events/poll.rb +0 -32
- data/lib/search_kit/events/poll/process.rb +0 -42
- data/lib/search_kit/events/publish.rb +0 -48
- data/lib/search_kit/indices.rb +0 -58
- data/lib/search_kit/indices/cli.rb +0 -65
- data/lib/search_kit/messaging.rb +0 -44
- data/lib/search_kit/search.rb +0 -30
- data/lib/search_kit/search/cli.rb +0 -49
- data/lib/search_kit/search/cli/actions.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 931bd5667f77b68c5150e37a0a4c07495f51574e
|
4
|
+
data.tar.gz: 2bbaf39e5215f1066277e42cc4696cd8fc704b28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90d6c826b8895c6907e4d13582b52ee1bb8680619ed1e155f4ad0854c0fae5fbc80862d683ed4dda11959f5ff83b5749914aa7a17e33f88dadc5ce8c55aed46f
|
7
|
+
data.tar.gz: 1e42b9b321acfb838218a63f0a3f68d0eb344f905e72c764caf905b8fd2af165034a4af2856da1800818d1c266ab23b263fe39ec09c55a81d13c640397151c72
|
data/.gitignore
CHANGED
data/config/locales/en.yml
CHANGED
@@ -1,6 +1,115 @@
|
|
1
1
|
en:
|
2
2
|
http:
|
3
|
-
400:
|
3
|
+
400: Bad request given
|
4
4
|
401:
|
5
|
-
inactive:
|
6
|
-
privilege:
|
5
|
+
inactive: Inactive or expired token
|
6
|
+
privilege: Inadequate permissions on token
|
7
|
+
404: Unable to find %{type} at the given uri
|
8
|
+
422: Options given unprocessable
|
9
|
+
cli:
|
10
|
+
errors:
|
11
|
+
json_parse: "%{type} must be given in the form of a JSON string."
|
12
|
+
no_server: No running SearchKit service found at %{uri}.
|
13
|
+
unreadable: "Response unreadable: %{error}"
|
14
|
+
documents:
|
15
|
+
update:
|
16
|
+
command: update SLUG ID DOCUMENT
|
17
|
+
summary: Update a document with a json string
|
18
|
+
detail: |
|
19
|
+
Updating a document usage and purpose
|
20
|
+
delete:
|
21
|
+
command: delete SLUG ID
|
22
|
+
summary: Delete a document
|
23
|
+
detail: |
|
24
|
+
Deleting a document usage and purpose
|
25
|
+
create:
|
26
|
+
command: create SLUG DOCUMENT
|
27
|
+
summary: Create a document with a json string
|
28
|
+
detail: |
|
29
|
+
Creating a document usage and purpose
|
30
|
+
show:
|
31
|
+
command: show SLUG ID
|
32
|
+
summary: View a document
|
33
|
+
detail: |
|
34
|
+
# View a document (detail)
|
35
|
+
|
36
|
+
I like to pretend this is in markdown, or can be done in markdown. But is it really necessary?
|
37
|
+
|
38
|
+
1. I have no idea.
|
39
|
+
|
40
|
+
2. Who knows.
|
41
|
+
events:
|
42
|
+
complete:
|
43
|
+
command: complete ID
|
44
|
+
summary: Complete event for a given ID
|
45
|
+
detail: |
|
46
|
+
Detail about event completion
|
47
|
+
success: Event %{id} completed
|
48
|
+
pending:
|
49
|
+
command: pending
|
50
|
+
summary: Get all pending events, --channel to filter by channel
|
51
|
+
detail: |
|
52
|
+
Get all pending events, --channel to filter by channel
|
53
|
+
success:
|
54
|
+
index:
|
55
|
+
discovered: "Pending events:"
|
56
|
+
empty: No pending events found
|
57
|
+
filtered:
|
58
|
+
discovered: "Pending events for channel `%{channel}`:"
|
59
|
+
empty: No pending events found for channel `%{channel}`
|
60
|
+
publish:
|
61
|
+
command: publish CHANNEL PAYLOAD
|
62
|
+
summary: Publish an event payload (as a json string) to CHANNEL
|
63
|
+
detail: |
|
64
|
+
Publish an event payload (as a json string) to CHANNEL
|
65
|
+
success: |
|
66
|
+
Event published, check status with this command:
|
67
|
+
|
68
|
+
search-kit events status %{id}
|
69
|
+
|
70
|
+
Or look at use this command to to see this event and all neighboring events:
|
71
|
+
|
72
|
+
search-kit events pending %{channel}
|
73
|
+
status:
|
74
|
+
command: status ID
|
75
|
+
summary: Check status of a specific event ID
|
76
|
+
detail: |
|
77
|
+
Check status of a specific event ID
|
78
|
+
success: "Event %{id} status: %{status}"
|
79
|
+
indices:
|
80
|
+
archive:
|
81
|
+
command: archive SLUG
|
82
|
+
summary: Archive an index
|
83
|
+
detail: |
|
84
|
+
Archive an index
|
85
|
+
create:
|
86
|
+
command: create NAME
|
87
|
+
summary: Create an index
|
88
|
+
detail: |
|
89
|
+
Create an index
|
90
|
+
scaffold:
|
91
|
+
command: scaffold NAME JSON
|
92
|
+
summary: Create an index and populate it with a list of documents
|
93
|
+
detail: |
|
94
|
+
Create an index and populate it with a list of documents
|
95
|
+
show:
|
96
|
+
command: show SLUG
|
97
|
+
summary: View an index
|
98
|
+
detail: |
|
99
|
+
View an index
|
100
|
+
update:
|
101
|
+
command: update SLUG
|
102
|
+
summary: Update an index
|
103
|
+
detail: |
|
104
|
+
In depth detail about index updating
|
105
|
+
search:
|
106
|
+
create:
|
107
|
+
command: search SLUG PHRASE
|
108
|
+
summary: Search an index
|
109
|
+
detail: |
|
110
|
+
Search an index
|
111
|
+
success:
|
112
|
+
headline: |
|
113
|
+
"Searching `%{slug}` for titles matching `%{phrase}`:"
|
114
|
+
info: |
|
115
|
+
" - Found %{search.results} titles in %{search.time}ms"
|
data/lib/search_kit.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require 'i18n'
|
3
3
|
require "search_kit/version"
|
4
|
+
require "search_kit/thor"
|
4
5
|
|
5
6
|
module SearchKit
|
6
7
|
autoload :CLI, 'search_kit/cli'
|
7
|
-
autoload :
|
8
|
+
autoload :Clients, 'search_kit/clients'
|
8
9
|
autoload :Configuration, 'search_kit/configuration'
|
9
|
-
autoload :Documents, 'search_kit/documents'
|
10
10
|
autoload :Errors, 'search_kit/errors'
|
11
|
-
autoload :Events, 'search_kit/events'
|
12
|
-
autoload :Indices, 'search_kit/indices'
|
13
11
|
autoload :Logger, 'search_kit/logger'
|
14
|
-
autoload :
|
15
|
-
autoload :
|
12
|
+
autoload :Messages, 'search_kit/messages'
|
13
|
+
autoload :Models, 'search_kit/models'
|
14
|
+
autoload :Polling, 'search_kit/polling'
|
16
15
|
|
17
16
|
def self.logger
|
18
17
|
@logger ||= Logger.new
|
data/lib/search_kit/cli.rb
CHANGED
@@ -1,33 +1,68 @@
|
|
1
|
+
require 'ansi'
|
2
|
+
require 'highline'
|
1
3
|
require 'thor'
|
4
|
+
require 'search_kit/thor'
|
2
5
|
|
3
6
|
module SearchKit
|
4
7
|
class CLI < Thor
|
5
|
-
|
8
|
+
autoload :Documents, 'search_kit/cli/documents'
|
9
|
+
autoload :Events, 'search_kit/cli/events'
|
10
|
+
autoload :Indices, 'search_kit/cli/indices'
|
11
|
+
autoload :Search, 'search_kit/cli/search'
|
12
|
+
autoload :Subscribers, 'search_kit/cli/subscribers'
|
13
|
+
|
14
|
+
no_commands do
|
15
|
+
def cli
|
16
|
+
@cli ||= HighLine.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def messages
|
20
|
+
@messages ||= Messages.new
|
21
|
+
end
|
22
|
+
end
|
6
23
|
|
7
24
|
desc "documents", "Manage individual SearchKit documents"
|
8
|
-
subcommand "documents", SearchKit::Documents
|
25
|
+
subcommand "documents", SearchKit::CLI::Documents
|
9
26
|
|
10
27
|
desc "events", "Publish and subscribe to SearchKit events"
|
11
|
-
subcommand "events", SearchKit::Events
|
28
|
+
subcommand "events", SearchKit::CLI::Events
|
12
29
|
|
13
30
|
desc "indices", "Manage your SearchKit indices"
|
14
|
-
subcommand "indices", SearchKit::Indices
|
31
|
+
subcommand "indices", SearchKit::CLI::Indices
|
15
32
|
|
16
33
|
desc "search", "Quickly search your indices"
|
17
|
-
subcommand "search", SearchKit::Search
|
34
|
+
subcommand "search", SearchKit::CLI::Search
|
35
|
+
|
36
|
+
desc "subscribers", "Register and control a subscriber account"
|
37
|
+
subcommand "subscribers", SearchKit::CLI::Subscribers
|
18
38
|
|
19
39
|
desc "config SETTING [VALUE]", "Configure or view your SearchKit settings"
|
20
40
|
def config(setting, value = nil)
|
21
41
|
if value
|
22
42
|
SearchKit.set_config(setting, value)
|
23
|
-
info "Set #{setting}: #{value}"
|
43
|
+
messages.info "Set #{setting}: #{value}"
|
24
44
|
else
|
25
45
|
value = SearchKit.show_config(setting)
|
26
|
-
info "SearchKit settings for #{setting}:"
|
27
|
-
info " - ~/.search-kit/config.yml: #{value}"
|
28
|
-
info " - ENV: #{ENV.fetch(setting.upcase, "Not set")}"
|
29
|
-
info " - Runtime: #{SearchKit.config.send(setting)}"
|
46
|
+
messages.info "SearchKit settings for #{setting}:"
|
47
|
+
messages.info " - ~/.search-kit/config.yml: #{value}"
|
48
|
+
messages.info " - ENV: #{ENV.fetch(setting.upcase, "Not set")}"
|
49
|
+
messages.info " - Runtime: #{SearchKit.config.send(setting)}"
|
30
50
|
end
|
31
51
|
end
|
52
|
+
|
53
|
+
desc "setup", "Set up your search-kit environment"
|
54
|
+
def setup
|
55
|
+
email = cli.ask("Email: ".ansi(:cyan))
|
56
|
+
password = cli.ask("Password: ".ansi(:cyan)) do |query|
|
57
|
+
query.echo = '*'
|
58
|
+
end
|
59
|
+
|
60
|
+
client = SearchKit::Clients::Subscribers.new
|
61
|
+
subscriber = client.create(email: email, password: password)
|
62
|
+
|
63
|
+
config("app_token", subscriber.creator_tokens.first)
|
64
|
+
messages.info("Alright! Your search-kit install has been set up.")
|
65
|
+
end
|
66
|
+
|
32
67
|
end
|
33
68
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'search_kit/thor'
|
3
|
+
|
4
|
+
module SearchKit
|
5
|
+
class CLI < Thor
|
6
|
+
class Documents < Thor
|
7
|
+
namespace :documents
|
8
|
+
|
9
|
+
no_commands do
|
10
|
+
def client
|
11
|
+
@client ||= SearchKit::Clients::Documents.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def messages
|
15
|
+
@messages ||= SearchKit::Messages.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
document :create
|
20
|
+
def create(slug, document)
|
21
|
+
document = JSON.parse(document, symbolize_names: true)
|
22
|
+
response = client.create(slug, document)
|
23
|
+
messages.info response.to_json
|
24
|
+
rescue Errors::IndexNotFound
|
25
|
+
messages.not_found
|
26
|
+
rescue Errors::BadRequest
|
27
|
+
messages.bad_request
|
28
|
+
rescue Errors::Unprocessable
|
29
|
+
messages.unprocessable
|
30
|
+
rescue Faraday::ConnectionFailed
|
31
|
+
messages.no_service
|
32
|
+
rescue JSON::ParserError
|
33
|
+
messages.json_parse_error
|
34
|
+
end
|
35
|
+
|
36
|
+
document :delete
|
37
|
+
def delete(slug, id)
|
38
|
+
response = client.delete(slug, id)
|
39
|
+
messages.info response.to_json
|
40
|
+
rescue Errors::IndexNotFound
|
41
|
+
messages.not_found
|
42
|
+
rescue Faraday::ConnectionFailed
|
43
|
+
messages.no_service
|
44
|
+
end
|
45
|
+
|
46
|
+
document :show
|
47
|
+
def show(slug, id)
|
48
|
+
response = client.show(slug, id)
|
49
|
+
messages.info response.to_json
|
50
|
+
rescue Errors::IndexNotFound
|
51
|
+
messages.not_found
|
52
|
+
rescue Faraday::ConnectionFailed
|
53
|
+
messages.no_service
|
54
|
+
end
|
55
|
+
|
56
|
+
document :update
|
57
|
+
def update(slug, id, document)
|
58
|
+
document = JSON.parse(document, symbolize_names: true)
|
59
|
+
response = client.update(slug, id, document)
|
60
|
+
messages.info response.to_json
|
61
|
+
rescue JSON::ParserError
|
62
|
+
messages.json_parse_error
|
63
|
+
rescue Errors::BadRequest
|
64
|
+
messages.bad_request
|
65
|
+
rescue Errors::IndexNotFound
|
66
|
+
messages.not_found
|
67
|
+
rescue Errors::Unprocessable
|
68
|
+
messages.unprocessable
|
69
|
+
rescue Faraday::ConnectionFailed
|
70
|
+
messages.no_service
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'search_kit/thor'
|
3
|
+
|
4
|
+
module SearchKit
|
5
|
+
class CLI < Thor
|
6
|
+
class Events < Thor
|
7
|
+
namespace :events
|
8
|
+
|
9
|
+
no_commands do
|
10
|
+
def client
|
11
|
+
@client ||= SearchKit::Clients::Events.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def messages
|
15
|
+
@messages ||= SearchKit::Messages.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
document :complete
|
20
|
+
def complete(id)
|
21
|
+
client.complete(id)
|
22
|
+
messages.info I18n.t('cli.events.complete.success', id: id)
|
23
|
+
rescue Errors::EventNotFound
|
24
|
+
messages.not_found
|
25
|
+
rescue Faraday::ConnectionFailed
|
26
|
+
messages.no_service
|
27
|
+
end
|
28
|
+
|
29
|
+
document :pending
|
30
|
+
def pending(channel = nil)
|
31
|
+
events = channel ? client.pending(channel) : client.index
|
32
|
+
|
33
|
+
message_path = %w(cli events pending success)
|
34
|
+
message_path << (channel ? 'filtered' : 'index')
|
35
|
+
message_path << (events.any? ? 'discovered' : 'empty')
|
36
|
+
message = I18n.t(message_path.join('.'), channel: channel)
|
37
|
+
|
38
|
+
messages.info(message)
|
39
|
+
events.each { |event| messages.info(event.to_json) }
|
40
|
+
rescue Errors::BadRequest
|
41
|
+
messages.bad_request
|
42
|
+
rescue Errors::Unauthorized
|
43
|
+
messages.unauthorized
|
44
|
+
rescue Errors::Unprocessable
|
45
|
+
messages.unprocessable
|
46
|
+
rescue Faraday::ConnectionFailed
|
47
|
+
messages.no_service
|
48
|
+
end
|
49
|
+
|
50
|
+
document :publish
|
51
|
+
def publish(channel, payload)
|
52
|
+
payload = JSON.parse(payload, symbolize_names: true)
|
53
|
+
event = client.publish(channel, payload)
|
54
|
+
|
55
|
+
message = I18n.t('cli.events.publish.success',
|
56
|
+
channel: channel,
|
57
|
+
id: event.id
|
58
|
+
)
|
59
|
+
|
60
|
+
messages.info(message)
|
61
|
+
rescue Errors::BadRequest
|
62
|
+
messages.bad_request
|
63
|
+
rescue Errors::Unauthorized
|
64
|
+
messages.unauthorized
|
65
|
+
rescue Errors::Unprocessable
|
66
|
+
messages.unprocessable
|
67
|
+
rescue Faraday::ConnectionFailed
|
68
|
+
messages.no_service
|
69
|
+
rescue JSON::ParserError
|
70
|
+
messages.json_parse_error
|
71
|
+
end
|
72
|
+
|
73
|
+
document :status
|
74
|
+
def status(id)
|
75
|
+
event = client.show(id)
|
76
|
+
status = event.state
|
77
|
+
|
78
|
+
message = I18n.t('cli.events.status.success', id: id, status: status)
|
79
|
+
messages.info(message)
|
80
|
+
rescue Errors::EventNotFound
|
81
|
+
messages.not_found
|
82
|
+
rescue Faraday::ConnectionFailed
|
83
|
+
messages.no_service
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'search_kit/thor'
|
3
|
+
|
4
|
+
module SearchKit
|
5
|
+
class CLI < Thor
|
6
|
+
class Indices < Thor
|
7
|
+
namespace :indices
|
8
|
+
|
9
|
+
no_commands do
|
10
|
+
def client
|
11
|
+
@client ||= SearchKit::Clients::Indices.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def messages
|
15
|
+
@messages ||= Messages.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
document :archive
|
20
|
+
def archive(slug)
|
21
|
+
response = client.archive(slug)
|
22
|
+
messages.info response.to_json
|
23
|
+
rescue Errors::Unauthorized
|
24
|
+
messages.unauthorized
|
25
|
+
rescue Errors::IndexNotFound
|
26
|
+
messages.not_found
|
27
|
+
rescue Faraday::ConnectionFailed
|
28
|
+
messages.no_service
|
29
|
+
end
|
30
|
+
|
31
|
+
document :create
|
32
|
+
def create(name)
|
33
|
+
response = client.create(name)
|
34
|
+
messages.info response.to_json
|
35
|
+
rescue Errors::Unauthorized
|
36
|
+
messages.unauthorized
|
37
|
+
rescue Errors::BadRequest
|
38
|
+
messages.bad_request
|
39
|
+
rescue Errors::Unprocessable
|
40
|
+
messages.unprocessable
|
41
|
+
rescue Faraday::ConnectionFailed
|
42
|
+
messages.no_service
|
43
|
+
end
|
44
|
+
|
45
|
+
document :scaffold
|
46
|
+
def scaffold(name, json = "[]")
|
47
|
+
documents = JSON.parse(json, symbolize_names: true)
|
48
|
+
response = client.scaffold(name, documents)
|
49
|
+
|
50
|
+
messages.info response.to_json
|
51
|
+
rescue Errors::Unauthorized
|
52
|
+
messages.unauthorized
|
53
|
+
rescue Errors::BadRequest
|
54
|
+
messages.bad_request
|
55
|
+
rescue Errors::Unprocessable
|
56
|
+
messages.unprocessable
|
57
|
+
rescue Faraday::ConnectionFailed
|
58
|
+
messages.no_service
|
59
|
+
rescue JSON::ParserError
|
60
|
+
messages.json_parse_error
|
61
|
+
end
|
62
|
+
|
63
|
+
document :show
|
64
|
+
def show(slug)
|
65
|
+
response = client.show(slug)
|
66
|
+
messages.info response.to_json
|
67
|
+
rescue Errors::Unauthorized
|
68
|
+
messages.unauthorized
|
69
|
+
rescue Errors::IndexNotFound
|
70
|
+
messages.not_found
|
71
|
+
rescue Faraday::ConnectionFailed
|
72
|
+
messages.no_service
|
73
|
+
end
|
74
|
+
|
75
|
+
document :update
|
76
|
+
def update(slug, update_json)
|
77
|
+
options = JSON.parse(update_json, symbolize_names: true)
|
78
|
+
response = client.update(slug, options)
|
79
|
+
messages.info response.to_json
|
80
|
+
rescue Errors::Unauthorized
|
81
|
+
messages.unauthorized
|
82
|
+
rescue Errors::BadRequest
|
83
|
+
messages.bad_request
|
84
|
+
rescue Errors::IndexNotFound
|
85
|
+
messages.not_found
|
86
|
+
rescue Errors::Unprocessable
|
87
|
+
messages.unprocessable
|
88
|
+
rescue Faraday::ConnectionFailed
|
89
|
+
messages.no_service
|
90
|
+
rescue JSON::ParserError
|
91
|
+
messages.json_parse_error
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|