archivesspace-client 0.1.6 → 0.1.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c49aac54a1b49b27bbff319aa83e0a6f7dd711a1e644324ae9d96155b06d417
4
- data.tar.gz: bf2fa7e423165f8e03282c718fe819fa7c5836e9090ceacbcf58e642e6ea1938
3
+ metadata.gz: 84c338c470de7977936e22dae4a7e410a289d3c8211a62927a739645410adad6
4
+ data.tar.gz: 90acc47cfd00eb091504c8219e7e6b4376213dc7fc816c43cdce2aa2df54bf81
5
5
  SHA512:
6
- metadata.gz: 7ba30d239390c5066bc5a673e532fb75a3d1fc4f994a8d2726494363f919e3656a5f0d1e7cb730797cbfcf24d404c0447ea1a2243aef6fe4bb9ac1af61117dc4
7
- data.tar.gz: 385795fdc7c63144d96696be8a39a3e6f6e4f6c95fa5040be5e9be8d7db7c6a03b4db066bc3900299bb4e05e8d3446a8fa69b9628452beb0b65b477302702ee6
6
+ metadata.gz: 68e243484eb0b0554fd225ab71775745bba1426edddf0916a21079e5bf13cd62e57ce1f36129e5c57a2d2fd8a817bd47b4fb68b6340ea9f430edc3380e2616dc
7
+ data.tar.gz: e3a05272dbdbcf2cef2b1ab59bdcff5aef5ef527ee62d31d8006d148c7869deba3c09b01120a975b6e3b6eb448086e3790fc84be82976fc8fafb8071cf43d991
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in archivesspace-client.gemspec
data/README.md CHANGED
@@ -77,14 +77,68 @@ See `helpers.rb` for more convenience methods such as `client.digital_objects` e
77
77
 
78
78
  **Setting a repository context**
79
79
 
80
- Update the `base_repo` configuration value to add a repository scope to requests (this is optional).
80
+ Use the `repository` method to add a repository scope to requests (this is optional).
81
81
 
82
82
  ```ruby
83
- client.config.base_repo = "repositories/2"
84
- client.get('digital_objects') # instead of "repositories/2/digital_objects" etc.
83
+ client.repository(2)
84
+ client.get('digital_objects', query: {page: 1}) # instead of "repositories/2/digital_objects" etc.
85
85
 
86
86
  # to reset
87
- client.config.base_repo = ""
87
+ client.repository(nil)
88
+ # or
89
+ client.use_global_repository
90
+ ```
91
+
92
+ ## Templates
93
+
94
+ Templates are an optional feature that can help simplify the effort of creating
95
+ json payloads for ArchivesSpace. Rather than construct the json programatically
96
+ according to the schemas a `.erb` template can be used to generate payloads
97
+ instead which are transformed to json automatically. There are a small number of
98
+ templates provided with the client, but you can create your own and access them
99
+ by setting the `ARCHIVESSPACE_CLIENT_TEMPLATES_PATH` envvar. A particularly simple
100
+ template might look like:
101
+
102
+ ```erb
103
+ {
104
+ "digital_object_id": "<%= data[:digital_object_id] %>",
105
+ "title": "<%= data[:title] %>"
106
+ }
107
+ ```
108
+
109
+ Practically speaking there isn't much benefit to this example, but in the case of
110
+ a more complex record structure where you want to populate deeply nested elements
111
+ using a flat file structure (like csv) this can be a very convenient way of
112
+ assembling the payload. To process a template:
113
+
114
+ ```ruby
115
+ data = { repo_code: 'ABC', name: 'ABC Archive', agent_contact_name: 'ABC Admin' }
116
+ json = ArchivesSpace::Template.process(:repository_with_agent, data)
117
+ response = client.post('/repositories/with_agent', json)
118
+ puts response.result.success? ? '=)' : '=('
119
+ ```
120
+
121
+ ## CLI
122
+
123
+ Create an `~/.asclientrc` file with a json version of the client configuration:
124
+
125
+ ```json
126
+ {
127
+ "base_uri": "https://archives.university.edu/api",
128
+ "base_repo": "",
129
+ "username": "admin",
130
+ "password": "123456",
131
+ "page_size": 50,
132
+ "throttle": 0,
133
+ "verify_ssl": false
134
+ }
135
+ ```
136
+
137
+ Run commands:
138
+
139
+ ```bash
140
+ # when using locally via the repo prefix commands with ./exe/ i.e. ./exe/asclient -v
141
+ asclient -v
88
142
  ```
89
143
 
90
144
  ## Development
@@ -110,7 +164,7 @@ bundle exec rake
110
164
  Bump version in `lib/archivesspace/client/version.rb` then:
111
165
 
112
166
  ```bash
113
- VERSION=0.1.6
167
+ VERSION=$NEW_VERSION
114
168
  gem build archivesspace-client
115
169
  git add . && git commit -m "Bump to $VERSION"
116
170
  git tag v$VERSION
data/Rakefile CHANGED
@@ -1,6 +1,14 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
3
2
 
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rspec/core/rake_task'
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ # require 'rubocop/rake_task'
9
+ # RuboCop::RakeTask.new
10
+
11
+ require 'cucumber/rake/task'
12
+ Cucumber::Rake::Task.new
13
+
14
+ task default: %i[spec cucumber]
@@ -1,31 +1,37 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'archivesspace/client/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "archivesspace-client"
8
+ spec.name = 'archivesspace-client'
8
9
  spec.version = ArchivesSpace::Client::VERSION
9
- spec.authors = ["Mark Cooper"]
10
- spec.email = ["mark.c.cooper@outlook.com"]
11
- spec.summary = %q{Interact with ArchivesSpace via its RESTful API.}
12
- spec.description = %q{Interact with ArchivesSpace via its RESTful API.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
10
+ spec.authors = ['Mark Cooper']
11
+ spec.email = ['mark.c.cooper@outlook.com']
12
+ spec.summary = 'Interact with ArchivesSpace via the API.'
13
+ spec.description = 'Interact with ArchivesSpace via the API.'
14
+ spec.homepage = ''
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.add_development_dependency "bundler"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "3.6.0"
24
- spec.add_development_dependency "vcr", "3.0.3"
25
- spec.add_development_dependency "webmock", "3.0.1"
26
- spec.add_development_dependency "awesome_print", "~> 1.8.0"
22
+ spec.add_development_dependency 'aruba'
23
+ spec.add_development_dependency 'awesome_print', '~> 1.8.0'
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'capybara_discoball'
26
+ spec.add_development_dependency 'cucumber'
27
+ spec.add_development_dependency 'json_spec'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '3.6.0'
30
+ spec.add_development_dependency 'vcr', '3.0.3'
31
+ spec.add_development_dependency 'webmock', '3.0.1'
27
32
 
28
- spec.add_dependency "httparty", "0.14.0"
29
- spec.add_dependency "json", "2.0.3"
30
- spec.add_dependency "nokogiri", "1.10.10"
33
+ spec.add_dependency 'dry-cli', '~> 0.7'
34
+ spec.add_dependency 'httparty', '~> 0.14'
35
+ spec.add_dependency 'json', '~> 2.0'
36
+ spec.add_dependency 'nokogiri', '~> 1.10'
31
37
  end
data/examples/export.rb CHANGED
@@ -1,33 +1,36 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
4
  require 'awesome_print'
3
5
  require 'archivesspace/client'
4
6
 
5
7
  # official sandbox
6
- config = ArchivesSpace::Configuration.new({
7
- base_uri: "http://sandbox.archivesspace.org/api",
8
- base_repo: "",
9
- username: "admin",
10
- password: "admin",
11
- page_size: 50,
12
- throttle: 0,
13
- verify_ssl: false,
14
- })
8
+ config = ArchivesSpace::Configuration.new(
9
+ {
10
+ base_uri: 'http://test.archivesspace.org/staff/api',
11
+ base_repo: '',
12
+ username: 'admin',
13
+ password: 'admin',
14
+ page_size: 50,
15
+ throttle: 0,
16
+ verify_ssl: false
17
+ }
18
+ )
15
19
 
16
20
  client = ArchivesSpace::Client.new(config).login
17
21
  client.config.throttle = 0.5
18
- client.config.base_repo = "repositories/2"
22
+ client.config.base_repo = 'repositories/2'
19
23
 
20
24
  begin
21
- # date -d '2015-07-01 00:00:00' +'%s' # 1435734000
22
- client.resources.each(query: { modified_since: "1435734000"}) do |resource|
25
+ # date -d '2021-02-01 00:00:00' +'%s' # 1612166400
26
+ client.resources(query: { modified_since: '1612166400' }).each do |resource|
23
27
  # for now we are just printing ...
24
28
  # but you would actually write to a zip file or whatever
25
29
  id = resource['uri'].split('/')[-1]
26
30
  opts = { include_unpublished: false }
27
31
  response = client.get("resource_descriptions/#{id}.xml", opts)
28
32
  puts Nokogiri::XML(response.body).to_xml
29
- break
30
33
  end
31
- rescue ArchivesSpace::RequestError => ex
32
- puts ex.message
33
- end
34
+ rescue ArchivesSpace::RequestError => e
35
+ puts e.message
36
+ end
@@ -1,15 +1,16 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
4
  require 'awesome_print'
3
5
  require 'archivesspace/client'
4
6
 
5
- username = "mrx"
6
- password = "123456"
7
+ username = 'admin'
8
+ password = 'admin'
7
9
 
8
10
  # default client connection: localhost:8089, admin, admin
9
11
  client = ArchivesSpace::Client.new.login
10
12
  begin
11
- client.password_reset username, password
12
- puts "Successfully updated password for #{username}."
13
- rescue Exception => ex
14
- puts "Failed to update password for #{username},\n#{ex.message}"
13
+ puts client.password_reset(username, password).parsed
14
+ rescue StandardError => e
15
+ puts "Failed to update password for #{username},\n#{e.message}"
15
16
  end
@@ -1,57 +1,60 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
4
  require 'awesome_print'
3
5
  require 'archivesspace/client'
4
6
 
5
7
  # official sandbox
6
- config = ArchivesSpace::Configuration.new({
7
- base_uri: "http://sandbox.archivesspace.org/api",
8
- base_repo: "",
9
- username: "admin",
10
- password: "admin",
11
- page_size: 50,
12
- throttle: 0,
13
- verify_ssl: false,
14
- })
15
-
16
- # default client connection: localhost:8089, admin, admin
8
+ config = ArchivesSpace::Configuration.new(
9
+ {
10
+ base_uri: 'http://sandbox.archivesspace.org/api',
11
+ base_repo: '',
12
+ username: 'admin',
13
+ password: 'admin',
14
+ page_size: 50,
15
+ throttle: 0,
16
+ verify_ssl: false
17
+ }
18
+ )
19
+
17
20
  client = ArchivesSpace::Client.new(config).login
18
21
 
19
22
  ap ArchivesSpace::Template.list # view available templates
20
23
 
21
24
  repo_data = {
22
- repo_code: "XYZ",
23
- name: "XYZ Archive",
24
- agent_contact_name: "John Doe",
25
+ repo_code: 'XYZ',
26
+ name: 'XYZ Archive',
27
+ agent_contact_name: 'XYZ Admin'
25
28
  }
26
29
 
27
30
  user_data = {
28
- username: "lmessi",
29
- name: "Lionel Messi",
30
- is_admin: true,
31
+ username: 'lmessi',
32
+ name: 'Lionel Messi',
33
+ is_admin: true
31
34
  }
32
- user_password = "123456"
35
+ user_password = '123456'
33
36
 
34
- repository = ArchivesSpace::Template.process_template(:repository_with_agent, repo_data)
37
+ repository = ArchivesSpace::Template.process(:repository_with_agent, repo_data)
35
38
 
36
39
  begin
37
40
  response = client.post('/repositories/with_agent', repository)
38
- if response.status_code.to_s =~ /^2/
39
- repository = client.repositories.find { |r| r["repo_code"] == "XYZ" }
41
+ if response.result.success?
42
+ repository = client.repositories.find { |r| r['repo_code'] == 'XYZ' }
40
43
  ap repository
41
- ap client.delete(repository["uri"])
44
+ ap client.delete(repository['uri'])
42
45
  else
43
46
  ap response.parsed
44
47
  end
45
48
 
46
- user = ArchivesSpace::Template.process_template(:user, user_data)
49
+ user = ArchivesSpace::Template.process(:user, user_data)
47
50
  response = client.post('users', user, { password: user_password })
48
- if response.status_code.to_s =~ /^2/
49
- user = client.users.find { |r| r["username"] == "lmessi" }
51
+ if response.result.success?
52
+ user = client.users.find { |r| r['username'] == 'lmessi' }
50
53
  ap user
51
- ap client.delete user["uri"]
54
+ ap client.delete user['uri']
52
55
  else
53
56
  ap response.parsed
54
57
  end
55
- rescue ArchivesSpace::RequestError => ex
56
- puts ex.message
58
+ rescue ArchivesSpace::RequestError => e
59
+ puts e.message
57
60
  end
@@ -1,17 +1,21 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
4
  require 'awesome_print'
3
5
  require 'archivesspace/client'
4
6
 
5
7
  # official sandbox
6
- config = ArchivesSpace::Configuration.new({
7
- base_uri: "http://sandbox.archivesspace.org/api",
8
- base_repo: "",
9
- username: "admin",
10
- password: "admin",
11
- page_size: 50,
12
- throttle: 0,
13
- verify_ssl: false,
14
- })
8
+ config = ArchivesSpace::Configuration.new(
9
+ {
10
+ base_uri: 'http://sandbox.archivesspace.org/api',
11
+ base_repo: '',
12
+ username: 'admin',
13
+ password: 'admin',
14
+ page_size: 50,
15
+ throttle: 0,
16
+ verify_ssl: false
17
+ }
18
+ )
15
19
 
16
20
  client = ArchivesSpace::Client.new(config).login
17
- puts client.get("version").body
21
+ puts client.get('version').body
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
+ require 'awesome_print'
5
+ require 'archivesspace/client'
6
+
7
+ # official sandbox
8
+ config = ArchivesSpace::Configuration.new(
9
+ {
10
+ base_uri: 'http://sandbox.archivesspace.org/api',
11
+ base_repo: '',
12
+ username: 'admin',
13
+ password: 'admin',
14
+ page_size: 50,
15
+ throttle: 0,
16
+ verify_ssl: false
17
+ }
18
+ )
19
+
20
+ client = ArchivesSpace::Client.new(config).login
21
+
22
+ user_data = {
23
+ username: 'bde',
24
+ name: 'BDE',
25
+ is_admin: false
26
+ }
27
+
28
+ client.post(
29
+ 'users',
30
+ ArchivesSpace::Template.process(:user, user_data),
31
+ { password: '123456' }
32
+ )
33
+
34
+ users_with_roles = {
35
+ 'bde' => ['repository-basic-data-entry']
36
+ }
37
+
38
+ begin
39
+ client.config.base_repo = "repositories/2"
40
+ results = client.group_user_assignment users_with_roles
41
+ ap results.map(&:parsed)
42
+ rescue ArchivesSpace::RequestError => e
43
+ puts e.message
44
+ end
data/exe/asclient ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'archivesspace/client'
6
+
7
+ Dry::CLI.new(ArchivesSpace::Client::CLI).call
@@ -0,0 +1,4 @@
1
+ Feature: Exec
2
+ Scenario: With an invalid request type
3
+ When I run `asclient exec all`
4
+ Then the output should match /asclient exec TYPE PATH/
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aruba/cucumber'
4
+ require 'json_spec/cucumber'
5
+ require 'capybara_discoball'
6
+
7
+ def last_json
8
+ last_command_started.output
9
+ end
@@ -0,0 +1,12 @@
1
+ Feature: Version
2
+ Scenario: Output with 'v'
3
+ When I run `asclient v`
4
+ Then the output should match /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/
5
+
6
+ Scenario: Output with -v
7
+ When I run `asclient -v`
8
+ Then the output should match /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/
9
+
10
+ Scenario: Output with --version
11
+ When I run `asclient --version`
12
+ Then the output should match /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArchivesSpace
4
+ class Client
5
+ module CLI
6
+ # ArchivesSpace::Client::CLI::Exec executes an API request
7
+ class Exec < Dry::CLI::Command
8
+ desc 'Execute an API request'
9
+
10
+ argument :type, required: true, values: %i[get post put delete], desc: 'API request type'
11
+ argument :path, required: true, desc: 'API request path'
12
+
13
+ option :rid, type: :integer, default: nil, desc: 'Repository id'
14
+ option :payload, type: :string, default: '{}', desc: 'Data payload (json)'
15
+ option :params, type: :string, default: '{}', desc: 'Params (json)'
16
+
17
+ example [
18
+ 'exec get --rid 2 "resources/1"',
19
+ 'exec get users --params \'{"query": {"page": 1}}\''
20
+ ]
21
+
22
+ def call(type:, path:, rid: nil, payload: '{}', params: '{}', **)
23
+ client = ArchivesSpace::Client::CLI.client
24
+ client.repository(rid) if rid
25
+ type = type.to_sym
26
+ payload = JSON.parse(payload, symbolize_names: true)
27
+ params = JSON.parse(params, symbolize_names: true)
28
+
29
+ response = case type
30
+ when :get
31
+ client.get(path, params)
32
+ when :post
33
+ client.post(path, payload, params)
34
+ when :put
35
+ client.put(path, payload, params)
36
+ when :delete
37
+ client.delete(path)
38
+ end
39
+ puts JSON.generate(response.parsed)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArchivesSpace
4
+ class Client
5
+ module CLI
6
+ # ArchivesSpace::Client::CLI::Version prints version
7
+ class Version < Dry::CLI::Command
8
+ desc 'Print ArchivesSpace Client version'
9
+
10
+ def call(*)
11
+ puts ArchivesSpace::Client::VERSION
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArchivesSpace
4
+ class Client
5
+ module CLI
6
+ extend Dry::CLI::Registry
7
+
8
+ def self.client
9
+ cfg = ArchivesSpace::Configuration.new(ArchivesSpace::Client::CLI.find_config)
10
+ ArchivesSpace::Client.new(cfg).login
11
+ end
12
+
13
+ def self.find_config
14
+ config = ENV.fetch('ASCLIENT_CFG', File.join(ENV['HOME'], '.asclientrc'))
15
+ raise "Unable to find asclient configuration file at: #{config}" unless File.file?(config)
16
+
17
+ JSON.parse(File.read(config), symbolize_names: true)
18
+ end
19
+
20
+ register 'exec', Exec, aliases: ['e', '-e']
21
+ register 'version', Version, aliases: ['v', '-v', '--version']
22
+ end
23
+ end
24
+ end
@@ -1,43 +1,66 @@
1
- module ArchivesSpace
1
+ # frozen_string_literal: true
2
2
 
3
+ module ArchivesSpace
3
4
  class Client
4
- include Helpers
5
+ include Pagination
6
+ include Task
5
7
  attr_accessor :token
6
8
  attr_reader :config
7
9
 
8
10
  def initialize(config = Configuration.new)
9
- unless config.kind_of? ArchivesSpace::Configuration
10
- raise "Invalid configuration object"
11
- end
11
+ raise 'Invalid configuration object' unless config.is_a? ArchivesSpace::Configuration
12
+
12
13
  @config = config
13
14
  @token = nil
14
15
  end
15
16
 
17
+ def backend_version
18
+ get 'version'
19
+ end
20
+
16
21
  def get(path, options = {})
17
22
  request 'GET', path, options
18
23
  end
19
24
 
20
25
  def post(path, payload, params = {})
21
- request 'POST', path, { body: payload.to_json, query: params }
26
+ request 'POST', path, { body: payload, query: params }
22
27
  end
23
28
 
24
29
  def put(path, payload, params = {})
25
- request 'PUT', path, { body: payload.to_json, query: params }
30
+ request 'PUT', path, { body: payload, query: params }
26
31
  end
27
32
 
28
33
  def delete(path)
29
34
  request 'DELETE', path
30
35
  end
31
36
 
37
+ # Scoping requests
38
+ def repository(id)
39
+ if id.nil?
40
+ use_global_repository
41
+ return
42
+ end
43
+
44
+ begin
45
+ Integer(id)
46
+ rescue StandardError
47
+ raise RepositoryIdError, "Invalid Repository id: #{id}"
48
+ end
49
+
50
+ @config.base_repo = "repositories/#{id}"
51
+ end
52
+
53
+ def use_global_repository
54
+ @config.base_repo = ''
55
+ end
56
+
32
57
  private
33
58
 
34
59
  def request(method, path, options = {})
35
60
  sleep config.throttle
36
- options[:headers] = { "X-ArchivesSpace-Session" => token } if token
61
+ options[:headers] = { 'X-ArchivesSpace-Session' => token } if token
37
62
  result = Request.new(config, method, path, options).execute
38
63
  Response.new result
39
64
  end
40
-
41
65
  end
42
-
43
66
  end