chimps-cli 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +20 -0
- data/README.rdoc +322 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/chimps +4 -0
- data/lib/chimps-cli.rb +46 -0
- data/lib/chimps-cli/commands.rb +179 -0
- data/lib/chimps-cli/commands/base.rb +65 -0
- data/lib/chimps-cli/commands/create.rb +38 -0
- data/lib/chimps-cli/commands/delete.rb +29 -0
- data/lib/chimps-cli/commands/destroy.rb +36 -0
- data/lib/chimps-cli/commands/download.rb +40 -0
- data/lib/chimps-cli/commands/get.rb +30 -0
- data/lib/chimps-cli/commands/help.rb +100 -0
- data/lib/chimps-cli/commands/list.rb +48 -0
- data/lib/chimps-cli/commands/me.rb +30 -0
- data/lib/chimps-cli/commands/post.rb +33 -0
- data/lib/chimps-cli/commands/put.rb +33 -0
- data/lib/chimps-cli/commands/query.rb +58 -0
- data/lib/chimps-cli/commands/search.rb +54 -0
- data/lib/chimps-cli/commands/show.rb +40 -0
- data/lib/chimps-cli/commands/test.rb +37 -0
- data/lib/chimps-cli/commands/update.rb +38 -0
- data/lib/chimps-cli/commands/upload.rb +86 -0
- data/lib/chimps-cli/utils.rb +13 -0
- data/lib/chimps-cli/utils/acts_on_resource.rb +93 -0
- data/lib/chimps-cli/utils/explicit_path.rb +30 -0
- data/lib/chimps-cli/utils/http_format.rb +51 -0
- data/lib/chimps-cli/utils/uses_param_value_data.rb +90 -0
- data/spec/chimps-cli/commands/delete_spec.rb +9 -0
- data/spec/chimps-cli/commands/get_spec.rb +8 -0
- data/spec/chimps-cli/commands/help_spec.rb +18 -0
- data/spec/chimps-cli/commands/list_spec.rb +7 -0
- data/spec/chimps-cli/commands/post_spec.rb +10 -0
- data/spec/chimps-cli/commands/put_spec.rb +10 -0
- data/spec/chimps-cli/commands/show_spec.rb +7 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/acts_on_resource.rb +22 -0
- data/spec/support/explicit_path.rb +42 -0
- data/spec/support/http_format.rb +23 -0
- data/spec/support/uses_param_value_data.rb +88 -0
- metadata +166 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
module Chimps
|
2
|
+
|
3
|
+
config.use :commands
|
4
|
+
|
5
|
+
# A namespace to hold the various commands Chimps defines.
|
6
|
+
module Commands
|
7
|
+
|
8
|
+
def self.class_for name
|
9
|
+
self.instance_eval(name.to_s.capitalize)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included obj
|
13
|
+
obj.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# Create a new command from the given +command_name+. The
|
18
|
+
# resulting command will be initialized but will not have been
|
19
|
+
# executed.
|
20
|
+
#
|
21
|
+
# @return [Chimps::Command]
|
22
|
+
def command
|
23
|
+
return unless Chimps.config.command
|
24
|
+
# Chimps.config.command_settings.resolve!
|
25
|
+
Chimps::Commands.class_for(Chimps.config.command_name).new(Chimps.config.command_settings)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def self.define_skip_column_names command
|
32
|
+
command.define :skip_column_names, :description => "Don't print column names in output (ignored unless TSV format)", :flag => :s, :type => :boolean
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.define_data_file command
|
36
|
+
command.define :data, :description => "Path to a .json or .yaml file.", :flag => :d
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.define_my command
|
40
|
+
command.define :my, :description => "List only resources owned by you.", :flag => :m, :type => :boolean
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.define_pretty_print command
|
44
|
+
command.define :pretty, :description => "Pretty-print output", :flag => :p, :type => :boolean
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.define_request_format command
|
48
|
+
command.define :request_format, :description => "The request format (json, xml, yaml) to request from Infochimps"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.define_response_format command
|
52
|
+
command.define :response_format, :description => "The response format (json, xml, yaml) to request from Infochimps"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.define_signable command
|
56
|
+
command.define :sign, :description => "Sign the request", :flag => :s, :type => :boolean
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.define_print_headers command
|
60
|
+
command.define :headers, :description => "Print response headers", :flag => :i
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.define_config
|
64
|
+
|
65
|
+
#
|
66
|
+
# HTTP verbs
|
67
|
+
#
|
68
|
+
Chimps.config.define_command :get, :description => "Send a GET request" do |command|
|
69
|
+
# define_request_format(command)
|
70
|
+
define_response_format(command)
|
71
|
+
define_signable(command)
|
72
|
+
define_print_headers(command)
|
73
|
+
end
|
74
|
+
|
75
|
+
Chimps.config.define_command :post, :description => "Send a POST request" do |command|
|
76
|
+
# define_request_format(command)
|
77
|
+
define_response_format(command)
|
78
|
+
define_data_file(command)
|
79
|
+
define_signable(command)
|
80
|
+
define_print_headers(command)
|
81
|
+
end
|
82
|
+
|
83
|
+
Chimps.config.define_command :put, :description => "Send a PUT request" do |command|
|
84
|
+
# define_request_format(command)
|
85
|
+
define_response_format(command)
|
86
|
+
define_data_file(command)
|
87
|
+
define_signable(command)
|
88
|
+
define_print_headers(command)
|
89
|
+
end
|
90
|
+
|
91
|
+
Chimps.config.define_command :delete, :description => "Send a DELETE request" do |command|
|
92
|
+
# define_request_format(command)
|
93
|
+
define_response_format(command)
|
94
|
+
define_signable(command)
|
95
|
+
define_print_headers(command)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
#
|
100
|
+
# Core REST actions
|
101
|
+
#
|
102
|
+
|
103
|
+
Chimps.config.define_command :list, :description => "List datasets, sources, users, &c." do |command|
|
104
|
+
define_response_format(command)
|
105
|
+
define_skip_column_names(command)
|
106
|
+
define_my(command)
|
107
|
+
define_pretty_print(command)
|
108
|
+
end
|
109
|
+
|
110
|
+
Chimps.config.define_command :show, :description => "Show a dataset, source, license, &c. in detail" do |command|
|
111
|
+
define_response_format(command)
|
112
|
+
define_response_format(command)
|
113
|
+
define_pretty_print(command)
|
114
|
+
end
|
115
|
+
|
116
|
+
Chimps.config.define_command :create, :description => "Create a new dataset, source, license, &c." do |command|
|
117
|
+
define_response_format(command)
|
118
|
+
define_data_file(command)
|
119
|
+
end
|
120
|
+
|
121
|
+
Chimps.config.define_command :update, :description => "Update an existing dataset, source, license, &c." do |command|
|
122
|
+
define_response_format(command)
|
123
|
+
define_data_file(command)
|
124
|
+
end
|
125
|
+
|
126
|
+
Chimps.config.define_command :destroy, :description => "Destroy an existing dataset, source, license, &c." do |command|
|
127
|
+
define_response_format(command)
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Workflows
|
132
|
+
#
|
133
|
+
|
134
|
+
Chimps.config.define_command :download, :description => "Download a dataset" do |command|
|
135
|
+
command.define :output, :description => "Path to output file (defaults to current directory)", :flag => :o, :type => String
|
136
|
+
end
|
137
|
+
|
138
|
+
# Chimps.config.define_command :upload, :description => "Upload a dataset" do |command|
|
139
|
+
# command.define :create, :description => "Create an empty upload", :flag => :C, :type => :boolean
|
140
|
+
# command.define :start, :description => "Start the processing of the upload", :flag => :s, :type => :boolean
|
141
|
+
# command.define :destroy, :description => "Stop (if started) and destroy the current upload", :flag => :D, :type => :boolean
|
142
|
+
# command.define :restart, :description => "Stop the current upload (if started) and begin anew", :flag => :r, :type => :boolean
|
143
|
+
# end
|
144
|
+
|
145
|
+
|
146
|
+
#
|
147
|
+
# Other Actions
|
148
|
+
#
|
149
|
+
|
150
|
+
|
151
|
+
Chimps.config.define_command :me, :description => "Show your profile" do |command|
|
152
|
+
define_response_format(command)
|
153
|
+
define_pretty_print(command)
|
154
|
+
end
|
155
|
+
|
156
|
+
Chimps.config.define_command :search, :description => 'Search datasets, sources, licenses, &c.' do |command|
|
157
|
+
define_skip_column_names(command)
|
158
|
+
define_my(command)
|
159
|
+
define_pretty_print(command)
|
160
|
+
define_response_format(command)
|
161
|
+
end
|
162
|
+
|
163
|
+
Chimps.config.define_command :query, :description => "Get a response from the Query API" do |command|
|
164
|
+
define_pretty_print(command)
|
165
|
+
define_data_file(command)
|
166
|
+
end
|
167
|
+
|
168
|
+
Chimps.config.define_command :test, :description => "Test your authentication credentials with Infochimps"
|
169
|
+
|
170
|
+
Chimps.config.define_help_command!
|
171
|
+
|
172
|
+
Chimps.config.commands.keys.each do |command|
|
173
|
+
autoload command.to_s.capitalize.to_sym, "chimps-cli/commands/#{command}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
define_config
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Chimps
|
2
|
+
|
3
|
+
# A base class from which to subclass specific commands. A subclass
|
4
|
+
# should
|
5
|
+
#
|
6
|
+
# - define class constants <tt>BANNER</tt> and <tt>HELP</tt> which
|
7
|
+
# - will display the appropriate help to the user.
|
8
|
+
#
|
9
|
+
# - add specific options by defining a method that begins with
|
10
|
+
# +define+ and ends with +options+ (i.e. - +define_output_options+
|
11
|
+
# to add options related to output).
|
12
|
+
#
|
13
|
+
# - define a method <tt>execute!</tt> which will actually run the
|
14
|
+
# command.
|
15
|
+
class Command
|
16
|
+
|
17
|
+
# Appears when printing help for this command, as the very first
|
18
|
+
# line. Should be one-line summary of how to use this command.
|
19
|
+
USAGE = "Define #{self}::USAGE when you subclass Chimps::Command"
|
20
|
+
|
21
|
+
# Appears when printing help for this command. Should consist of
|
22
|
+
# general help or examples of the command iteslf. Help on
|
23
|
+
# specific options is automatically generated.
|
24
|
+
HELP = "Define #{self}::HELP when you subclass Chimps::Command"
|
25
|
+
|
26
|
+
# The configuration settings for this command.
|
27
|
+
#
|
28
|
+
# @return [Configliere::Param]
|
29
|
+
attr_accessor :config
|
30
|
+
|
31
|
+
# Create a new command. Will define options specific to
|
32
|
+
# subclases, parse the given +argv+, and load the global Chimps
|
33
|
+
# configuration. Will _not_ execute the command.
|
34
|
+
#
|
35
|
+
# @param [Configliere::Param]
|
36
|
+
# @return [Chimps::Command]
|
37
|
+
def initialize config
|
38
|
+
self.config = config
|
39
|
+
end
|
40
|
+
|
41
|
+
# The name of this command, including the
|
42
|
+
# <tt>Chimps::Commands</tt> prefix.
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
def self.name
|
46
|
+
self.to_s.downcase
|
47
|
+
end
|
48
|
+
|
49
|
+
# The name of this command, excluding the
|
50
|
+
# <tt>Chimps::Commands</tt> prefix.
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
def name
|
54
|
+
self.class.name.split('::').last
|
55
|
+
end
|
56
|
+
|
57
|
+
# Run this command.
|
58
|
+
#
|
59
|
+
# Will raise a NotImplementedError for Chimps::Command itself --
|
60
|
+
# subclasses are expected to redefine this method.
|
61
|
+
def execute!
|
62
|
+
raise NotImplementedError.new("Redefine the `execute!' method in a subclass of #{self.class}.")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
# A command to issue a POST requst to create a resource at
|
5
|
+
# Infochimps.
|
6
|
+
class Create < Chimps::Command
|
7
|
+
|
8
|
+
include Chimps::Utils::ActsOnResource
|
9
|
+
include Chimps::Utils::UsesParamValueData
|
10
|
+
|
11
|
+
def self.allowed_models
|
12
|
+
%w[dataset source license]
|
13
|
+
end
|
14
|
+
|
15
|
+
USAGE = "usage: chimps create [OPTIONS] [PROP=VALUE] ..."
|
16
|
+
HELP = <<EOF
|
17
|
+
|
18
|
+
Create a #{default_resource_type} using the properties and values supplied.
|
19
|
+
|
20
|
+
#{how_to_input_data}
|
21
|
+
#{resources_listing}
|
22
|
+
|
23
|
+
Examples:
|
24
|
+
|
25
|
+
$ chimps create title='My Awesome Dataset' description="It is cool"
|
26
|
+
$ chimps create source -d my_source.yml
|
27
|
+
EOF
|
28
|
+
|
29
|
+
# Issue the POST request.
|
30
|
+
def execute!
|
31
|
+
ensure_data_is_present!
|
32
|
+
Request.new(resources_path, :body => { resource_type => data}, :sign => true).post.print(:yaml => true)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
class Delete < Chimps::Command
|
5
|
+
|
6
|
+
include Chimps::Utils::HttpFormat
|
7
|
+
include Chimps::Utils::ExplicitPath
|
8
|
+
|
9
|
+
USAGE = "usage: chimps delete [OPTIONS] PATH"
|
10
|
+
HELP = <<EOF
|
11
|
+
|
12
|
+
Send a DELETE request to the given PATH at Infochimps.
|
13
|
+
|
14
|
+
Examples:
|
15
|
+
|
16
|
+
$ chimps delete /datasets/my-crappy-dataset
|
17
|
+
$ chimps delete /sources/my-broken-source
|
18
|
+
EOF
|
19
|
+
|
20
|
+
def execute!
|
21
|
+
response = Chimps::Request.new(path, :query_params => query_params, :sign => config[:sign]).delete(headers)
|
22
|
+
response.print_headers if config[:headers]
|
23
|
+
response.print
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
# A command to issue a DELETE request against a resource at
|
5
|
+
# Infochimps.
|
6
|
+
class Destroy < Chimps::Command
|
7
|
+
|
8
|
+
include Chimps::Utils::ActsOnResource
|
9
|
+
|
10
|
+
def self.allowed_models
|
11
|
+
%w[dataset source license]
|
12
|
+
end
|
13
|
+
|
14
|
+
USAGE = "usage: chimps destroy [OPTIONS] [RESOURCE] ID_OR_SLUG"
|
15
|
+
HELP = <<EOF
|
16
|
+
|
17
|
+
Destroys a #{default_resource_type} identified by the given ID or slug.
|
18
|
+
|
19
|
+
#{resources_listing}
|
20
|
+
|
21
|
+
You can only destroy resources that you own.
|
22
|
+
|
23
|
+
Examples:
|
24
|
+
|
25
|
+
$ chimps destroy my-crappy-dataset
|
26
|
+
$ chimps destroy source 7837
|
27
|
+
EOF
|
28
|
+
|
29
|
+
def execute!
|
30
|
+
Request.new(resource_path, :sign => true).delete.print
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
# A command to download data from Infochimps.
|
5
|
+
class Download < Chimps::Command
|
6
|
+
|
7
|
+
include Chimps::Utils::ActsOnResource
|
8
|
+
|
9
|
+
USAGE = "usage: chimps download [OPTIONS] ID_OR_HANDLE"
|
10
|
+
HELP = <<EOF
|
11
|
+
|
12
|
+
Download a dataset identified by the given ID_OR_HANDLE.
|
13
|
+
|
14
|
+
If the dataset isn't free, you'll have to have purchased it first on
|
15
|
+
Infochimps.
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
|
19
|
+
# download to the current directory
|
20
|
+
$ chimps download my-awesome-dataset
|
21
|
+
|
22
|
+
# download to /tmp directory
|
23
|
+
$ chimps download my-awesome-dataset --output=/tmp
|
24
|
+
|
25
|
+
# save as ~/data.tar.gz (dangerous if you change the extension!)
|
26
|
+
$ chimps download my-awesome-dataset --output=~/data.tar.gz
|
27
|
+
EOF
|
28
|
+
|
29
|
+
def local_path
|
30
|
+
config[:output].blank? ? Dir.pwd : File.expand_path(config[:output])
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute!
|
34
|
+
Chimps::Download.new(resource_identifier).download(local_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
class Get < Chimps::Command
|
5
|
+
|
6
|
+
include Chimps::Utils::HttpFormat
|
7
|
+
include Chimps::Utils::ExplicitPath
|
8
|
+
|
9
|
+
USAGE = "usage: chimps get [OPTIONS] PATH"
|
10
|
+
HELP = <<EOF
|
11
|
+
|
12
|
+
Send a GET request to the given PATH at Infochimps.
|
13
|
+
|
14
|
+
Examples:
|
15
|
+
|
16
|
+
$ chimps get /datasets # list datasets (#{default_response_fmt} by default)
|
17
|
+
$ chimps get /datasets --response_format=yaml # in YAML
|
18
|
+
$ chimps get /sources/1.xml # look at source 1 in XML
|
19
|
+
EOF
|
20
|
+
|
21
|
+
def execute!
|
22
|
+
response = Chimps::Request.new(path, :query_params => query_params, :sign => config[:sign]).get(headers)
|
23
|
+
response.print_headers if config[:headers]
|
24
|
+
response.print
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Commands
|
3
|
+
class Help < Chimps::Command
|
4
|
+
|
5
|
+
USAGE = "usage: chimps help [OPTIONS] [COMMAND]"
|
6
|
+
|
7
|
+
HELP = <<EOF
|
8
|
+
This is the Infochimps command-line client. You can use it to search,
|
9
|
+
browse, create, edit, or delete data and metadata in the Infochimps
|
10
|
+
repository at http://www.infochimps.com.
|
11
|
+
|
12
|
+
Before you can create, edit, or delete anything you'll need to get an
|
13
|
+
Infochimps account at http://www.infochimps.org/signup. You'll
|
14
|
+
automatically be granted an API key.
|
15
|
+
|
16
|
+
But you can still browse, search, and download (free) data
|
17
|
+
immediately.
|
18
|
+
|
19
|
+
Learn more about the Infochimps API which powers this tool at
|
20
|
+
|
21
|
+
http://www.infochimps.com/apis
|
22
|
+
|
23
|
+
= Commands
|
24
|
+
|
25
|
+
chimps is a wrapper over the RESTful Infochimps API. It exposes the
|
26
|
+
following core actions
|
27
|
+
|
28
|
+
$ chimps list
|
29
|
+
$ chimps show
|
30
|
+
$ chimps create
|
31
|
+
$ chimps update
|
32
|
+
$ chimps destroy
|
33
|
+
|
34
|
+
for datasets (as well as other selected resources). It also helps
|
35
|
+
automate the workflow of uploading and downloading data with
|
36
|
+
|
37
|
+
$ chimps upload
|
38
|
+
$ chimps download
|
39
|
+
|
40
|
+
You can also make queries against the Infochimps Query API with
|
41
|
+
|
42
|
+
$ chimps query
|
43
|
+
|
44
|
+
learn more about the Infochimps Query API at
|
45
|
+
http://www.infochimps.com/api.
|
46
|
+
|
47
|
+
Finally, you can test that your system is configured properly and that
|
48
|
+
you can authenticate with Infochimps with
|
49
|
+
|
50
|
+
$ chimps test
|
51
|
+
|
52
|
+
Get more help on a specific command with
|
53
|
+
|
54
|
+
$ chimps help COMMAND
|
55
|
+
|
56
|
+
for any of the commands above.
|
57
|
+
|
58
|
+
= Setup
|
59
|
+
|
60
|
+
Once you have obtained an API key and secret from Infochimps, place
|
61
|
+
them in a file #{Chimps.config[:config]} in your home directory
|
62
|
+
with the following format
|
63
|
+
|
64
|
+
---
|
65
|
+
# in #{Chimps.config[:config]}
|
66
|
+
|
67
|
+
# API credentials for use with the Infochimps Dataset API
|
68
|
+
:site:
|
69
|
+
:key: oreeph6giedaeL3
|
70
|
+
:secret: Queechei6cu8chiuyiig8cheg5Ahx0boolaizi1ohtarooFu1doo5ohj5ohp9eehae5hakoongahghohgoi7yeihohx1eidaeng0eaveefohchoh6WeeV1EM
|
71
|
+
|
72
|
+
# API credentials for use on the Infochimps Query API
|
73
|
+
:query:
|
74
|
+
:key: zei7eeloShoah3Ce
|
75
|
+
EOF
|
76
|
+
|
77
|
+
def command_name
|
78
|
+
config.argv[1]
|
79
|
+
end
|
80
|
+
|
81
|
+
def command
|
82
|
+
@command ||= Chimps::Commands.class_for(command_name).new(Chimps.config.commands[command_name][:config])
|
83
|
+
end
|
84
|
+
|
85
|
+
def execute!
|
86
|
+
if config.argv.size > 1 && Chimps.config.command?(command_name)
|
87
|
+
$stderr.puts command.class::USAGE
|
88
|
+
$stderr.puts command.class::HELP
|
89
|
+
command.config.dump_basic_help "Additional options accepted by all commands:"
|
90
|
+
else
|
91
|
+
$stderr.puts self.class::USAGE
|
92
|
+
$stderr.puts self.class::HELP
|
93
|
+
end
|
94
|
+
Chimps.config.dump_basic_help
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|