collectionspace-client 0.1.5 → 0.14.1

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/publish.yml +42 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +8 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +2 -0
  7. data/README.md +19 -65
  8. data/Rakefile +44 -3
  9. data/bin/console +26 -0
  10. data/bin/rspec +29 -0
  11. data/collectionspace-client.gemspec +21 -19
  12. data/examples/demo.rb +14 -13
  13. data/examples/media_with_external_file.rb +20 -18
  14. data/examples/purge_empty_vocabs.rb +22 -0
  15. data/examples/reports.rb +178 -0
  16. data/examples/search.rb +25 -58
  17. data/examples/update_password.rb +3 -0
  18. data/lib/collectionspace/client/client.rb +19 -14
  19. data/lib/collectionspace/client/configuration.rb +16 -17
  20. data/lib/collectionspace/client/helpers.rb +145 -99
  21. data/lib/collectionspace/client/refname.rb +114 -0
  22. data/lib/collectionspace/client/request.rb +17 -13
  23. data/lib/collectionspace/client/response.rb +15 -10
  24. data/lib/collectionspace/client/search.rb +12 -7
  25. data/lib/collectionspace/client/service.rb +198 -0
  26. data/lib/collectionspace/client/template.rb +26 -0
  27. data/lib/collectionspace/client/templates/reindex_by_csids.xml.erb +10 -0
  28. data/lib/collectionspace/client/templates/reindex_by_doctype.xml.erb +5 -0
  29. data/lib/collectionspace/client/templates/reindex_full_text.xml.erb +51 -0
  30. data/lib/collectionspace/client/templates/report.xml.erb +16 -0
  31. data/lib/collectionspace/client/templates/reset_media_blob.xml.erb +6 -0
  32. data/lib/collectionspace/client/version.rb +3 -1
  33. data/lib/collectionspace/client.rb +20 -11
  34. metadata +64 -9
  35. data/examples/export.rb +0 -31
  36. data/examples/purge-empty-vocabularies.rb +0 -17
data/examples/search.rb CHANGED
@@ -1,67 +1,34 @@
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 'collectionspace/client'
4
6
 
5
- # CREATE CLIENT WITH DEFAULT (DEMO) CONFIGURATION -- BE NICE!!!
6
- client = CollectionSpace::Client.new
7
-
8
- # EXAMPLE NESTED ATTRIBUTE SEARCH
9
- search_args = {
10
- path: "collectionobjects",
11
- type: "collectionobjects_common",
12
- field: 'titleGroupList/*1/title',
13
- expression: "ILIKE '%blue%'",
14
- }
15
-
16
- query = CollectionSpace::Search.new.from_hash search_args
17
- ap client.search(query).parsed
7
+ config = CollectionSpace::Configuration.new(
8
+ base_uri: 'https://core.dev.collectionspace.org/cspace-services',
9
+ username: 'admin@core.collectionspace.org',
10
+ password: 'Administrator'
11
+ )
18
12
 
19
- # SEARCH AND REFORMAT RESULTS
20
-
21
- # assume retrieved for collectionobject i.e.: AttributeMap.where(type: 'collectionobject')
22
- attribute_map = [
23
- { field: 'title', key: 'collectionobjects_common', nested_key: 'title', with: nil },
24
- { field: 'title_type', key: 'collectionobjects_common', nested_key: 'titleType', with: nil },
25
- { field: 'display_date', key: 'collectionobjects_common', nested_key: 'dateDisplayDate', with: nil },
26
- { field: 'object_production_person_group', key: 'collectionobjects_common', nested_key: 'objectProductionPersonGroup', with: 'objectProductionPerson' },
27
- { field: 'content_persons', key: 'collectionobjects_common', nested_key: 'contentPersons', with: 'contentPerson' },
28
- { field: 'responsible_department', key: 'collectionobjects_common', nested_key: 'responsibleDepartment', with: nil },
29
- { field: 'created_by', key: 'collectionspace_core', nested_key: 'createdBy', with: nil },
30
- { field: 'created_at', key: 'collectionspace_core', nested_key: 'createdAt', with: nil },
31
- { field: 'updated_at', key: 'collectionspace_core', nested_key: 'updatedAt', with: nil },
32
- ]
13
+ client = CollectionSpace::Client.new(config)
33
14
 
34
15
  search_args = {
35
- path: "collectionobjects",
36
- type: "collectionspace_core",
37
- field: 'updatedAt',
38
- expression: ">= TIMESTAMP '2015-12-17T00:00:00'",
16
+ path: 'groups',
17
+ namespace: 'groups_common',
18
+ field: 'title',
19
+ expression: "ILIKE '%D%'"
39
20
  }
40
21
 
41
- query = CollectionSpace::Search.new.from_hash search_args
42
-
43
- result = client.search(query)
44
- if result.status_code == 200
45
- data = result.parsed["abstract_common_list"]["list_item"]
46
- data.each do |item|
47
- record = client.get(item["uri"]).parsed
48
-
49
- attributes = {}
50
- attribute_map.each do |map|
51
- if map[:with]
52
- as = client.deep_find(record, map[:key], map[:nested_key])
53
- values = []
54
- if as.is_a? Array
55
- values = as.map { |a| client.strip_refname( client.deep_find(a, map[:with]) ) }
56
- elsif as.is_a? Hash and as[ map[:with] ]
57
- values = as[ map[:with] ].is_a?(Array) ? as[ map[:with] ].map { |a| client.strip_refname(a) } : [ client.strip_refname(as[ map[:with] ]) ]
58
- end
59
- attributes[map[:field]] = values
60
- else
61
- attributes[map[:field]] = client.deep_find(record, map[:key], map[:nested_key])
62
- end
63
- end
64
- # PRINT REFORMATTED RESULTS
65
- ap attributes
22
+ puts 'Search: %D'
23
+ response = client.search(
24
+ CollectionSpace::Search.new.from_hash(search_args),
25
+ { sortBy: CollectionSpace::Search::DEFAULT_SORT }
26
+ )
27
+ if response.result.success?
28
+ response.parsed['abstract_common_list']['list_item'].map do |i|
29
+ puts i['uri']
66
30
  end
67
- end
31
+ end
32
+
33
+ puts 'Object and authority term searches have been moved to spec.'
34
+ puts 'See helpers_spec.rb examples describing find'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ puts 'Update password has been moved to Rake task: `cli:update_password[args...]`'
@@ -1,12 +1,16 @@
1
- module CollectionSpace
1
+ # frozen_string_literal: true
2
2
 
3
+ module CollectionSpace
4
+ # CollectionSpace client
3
5
  class Client
4
- include DeepFind
5
6
  include Helpers
6
7
  attr_reader :config
7
8
 
8
9
  def initialize(config = Configuration.new)
9
- raise "Invalid configuration object" unless config.kind_of? CollectionSpace::Configuration
10
+ unless config.is_a? CollectionSpace::Configuration
11
+ raise CollectionSpace::ArgumentError, 'Invalid configuration object'
12
+ end
13
+
10
14
  @config = config
11
15
  end
12
16
 
@@ -14,15 +18,14 @@ module CollectionSpace
14
18
  request 'GET', path, options
15
19
  end
16
20
 
17
- # additional_options: { query: { foo: 'bar' } }
18
- def post(path, payload, additional_options = {})
19
- raise PayloadError.new, Nokogiri::XML(payload).errors if Nokogiri::XML(payload).errors.any?
20
- request 'POST', path, { body: payload }.merge(additional_options)
21
+ def post(path, payload, options = {})
22
+ check_payload(payload)
23
+ request 'POST', path, { body: payload }.merge(options)
21
24
  end
22
25
 
23
- def put(path, payload)
24
- raise PayloadError.new, Nokogiri::XML(payload).errors if Nokogiri::XML(payload).errors.any?
25
- request 'PUT', path, { body: payload }
26
+ def put(path, payload, options = {})
27
+ check_payload(payload)
28
+ request 'PUT', path, { body: payload }.merge(options)
26
29
  end
27
30
 
28
31
  def delete(path)
@@ -31,12 +34,14 @@ module CollectionSpace
31
34
 
32
35
  private
33
36
 
37
+ def check_payload(payload)
38
+ errors = Nokogiri::XML(payload).errors
39
+ raise CollectionSpace::PayloadError, errors if errors.any?
40
+ end
41
+
34
42
  def request(method, path, options = {})
35
43
  sleep config.throttle
36
- result = Request.new(config, method, path, options).execute
37
- Response.new result
44
+ Response.new(Request.new(config, method, path, options).execute)
38
45
  end
39
-
40
46
  end
41
-
42
47
  end
@@ -1,28 +1,27 @@
1
- module CollectionSpace
1
+ # frozen_string_literal: true
2
2
 
3
+ module CollectionSpace
4
+ # CollectionSpace configuration
3
5
  class Configuration
6
+ DEFAULTS = {
7
+ base_uri: nil,
8
+ username: nil,
9
+ password: nil,
10
+ page_size: 25,
11
+ include_deleted: false,
12
+ throttle: 0,
13
+ verify_ssl: true
14
+ }.freeze
4
15
 
5
- def defaults
6
- {
7
- base_uri: "https://core.collectionspace.org/cspace-services",
8
- username: "admin@core.collectionspace.org",
9
- password: "Administrator",
10
- page_size: 50,
11
- include_deleted: false,
12
- throttle: 0,
13
- verify_ssl: true,
14
- }
15
- end
16
+ attr_accessor :base_uri, :username, :password, :page_size, :include_deleted, :throttle, :verify_ssl
16
17
 
17
18
  def initialize(settings = {})
18
- settings = defaults.merge(settings)
19
+ settings = DEFAULTS.merge(settings)
19
20
  settings.each do |property, value|
20
- next unless defaults.keys.include? property
21
+ next unless DEFAULTS.key?(property)
22
+
21
23
  instance_variable_set("@#{property}", value)
22
- self.class.send(:attr_accessor, property)
23
24
  end
24
25
  end
25
-
26
26
  end
27
-
28
27
  end
@@ -1,122 +1,168 @@
1
- module CollectionSpace
1
+ # frozen_string_literal: true
2
2
 
3
- # http://www.garrettqmartin.com/2015/02/03/finding-deeply-nested-hash-keys/
4
- module DeepFind
5
- def deep_find(obj, key, nested_key = nil)
6
- return obj[key] if obj.respond_to?(:key?) && obj.key?(key)
7
- if obj.is_a? Enumerable
8
- found = nil
9
- obj.find { |*a| found = deep_find(a.last, key) }
10
- if nested_key
11
- deep_find(found, nested_key)
12
- else
13
- found
14
- end
15
- end
3
+ module CollectionSpace
4
+ # Helper methods for client requests
5
+ module Helpers
6
+ # add / update batch job
7
+ def add_batch_job(name, template, data = {}, params = { pgSz: 100 })
8
+ payload = Template.process(template, data)
9
+ response = get('batch', { query: params })
10
+ create_or_update(response, 'batch', 'name', name, payload)
16
11
  end
17
- end
18
12
 
19
- module Helpers
13
+ # add / update reports
14
+ def add_report(data = {}, params = { pgSz: 100 })
15
+ payload = Template.process('report', data)
16
+ response = get('reports', { query: params })
17
+ create_or_update(response, 'reports', 'name', data[:name], payload)
18
+ end
20
19
 
21
20
  # get ALL records at path by paging through record set
22
21
  def all(path, options = {})
23
22
  list_type, list_item = get_list_types(path)
24
- Enumerator.new do |yielder|
25
- page = 0
26
- loop do
27
- result = request('GET', path, options.merge(query: { pgNum: page }))
28
- raise StopIteration unless result.parsed[list_type].key?('itemsInPage')
23
+ iterations = (count(path).to_f / config.page_size).ceil
24
+ return [] unless iterations.positive?
25
+
26
+ Enumerator::Lazy.new(0...iterations) do |yielder, i|
27
+ response = request('GET', path, options.merge(query: { pgNum: i }))
28
+ raise CollectionSpace::RequestError, response.result.body unless response.result.success?
29
29
 
30
- items = result.parsed[list_type]['itemsInPage'].to_i
31
- raise StopIteration if items == 0
30
+ items_in_page = response.parsed[list_type].fetch('itemsInPage', 0).to_i
31
+ list_items = items_in_page.positive? ? response.parsed[list_type][list_item] : []
32
+ list_items = [list_items] if items_in_page == 1
32
33
 
33
- list_items = result.parsed[list_type][list_item]
34
- list_items = [list_items] if items == 1
35
- list_items.each { |item| yielder << item }
36
- page += 1
37
- end
38
- end.lazy
34
+ yielder << list_items.shift until list_items.empty?
35
+ end
39
36
  end
40
37
 
41
38
  def count(path)
42
- # make sure path is only 1 level deep?
43
- count = nil
44
- result = request('GET', path, { query: { pgSz: 1 } })
45
- count = result.parsed['abstract_common_list']['totalItems'].to_i if result.status_code == 200
46
- count
47
- end
48
-
49
- # create blob record by external url
50
- def post_blob_url(url)
51
- raise ArgumentError.new("Invalid blob URL #{url}") unless URI.parse(url).scheme =~ /^https?/
52
- request 'POST', "blobs", {
53
- query: { "blobUri" => url },
54
- }
55
- end
56
-
57
- def post_relationship(type_a, csid_a, type_b, csid_b)
58
- # requires an erb template
59
- # request 'POST', "relations", { body: payload }
60
- # flip for reciprocal relationship
61
- # request 'POST', "relations", { body: payload }
62
- end
63
-
64
- def search(query, options = {})
65
- options = prepare_query(query, options)
66
- request "GET", query.path, options
67
- end
68
-
69
- def search_all(query, options = {}, &block)
70
- options = prepare_query(query, options)
71
- all query.path, options, &block
72
- end
73
-
74
- def strip_refname(refname)
75
- stripped = refname.match(/('.*')/)[0].delete("'") rescue ''
76
- stripped
77
- end
78
-
79
- # parsed record and map to get restructured object
80
- def to_object(record, attribute_map, stringify_keys = false)
81
- attributes = {}
82
- attribute_map.each do |map|
83
- map = map.inject({}) { |memo,(k,v)| memo[k.to_s] = v; memo} if stringify_keys
84
- if map["with"]
85
- as = deep_find(record, map["key"], map["nested_key"])
86
- values = []
87
- if as.is_a? Array
88
- values = as.map { |a| strip_refname( deep_find(a, map["with"]) ) }
89
- elsif as.is_a? Hash and as[ map["with"] ]
90
- values = as[ map["with"] ].is_a?(Array) ? as[ map["with"] ].map { |a| strip_refname(a) } : [ strip_refname(as[ map["with"] ]) ]
91
- end
92
- attributes[map["field"]] = values
93
- else
94
- attributes[map["field"]] = deep_find(record, map["key"], map["nested_key"])
95
- end
96
- end
97
- attributes
39
+ list_type, = get_list_types(path)
40
+ response = request('GET', path, query: { pgNum: 0, pgSz: 1 })
41
+ raise CollectionSpace::RequestError, response.result.body unless response.result.success?
42
+
43
+ response.parsed[list_type]['totalItems'].to_i
98
44
  end
99
45
 
100
- private
46
+ # get the tenant domain from a system required top level authority (person)
47
+ def domain
48
+ path = 'personauthorities'
49
+ response = request('GET', path, query: { pgNum: 0, pgSz: 1 })
50
+ raise CollectionSpace::RequestError, response.result.body unless response.result.success?
51
+
52
+ refname = response.parsed.dig(*get_list_types(path), 'refName')
53
+ CollectionSpace::RefName.parse(refname)[:domain]
54
+ end
55
+
56
+ # find procedure or object by type and id
57
+ # find authority/vocab term by type, subtype, and refname
58
+ # rubocop:disable Metrics/ParameterLists
59
+ def find(type:, value:, subtype: nil, field: nil, schema: 'common', sort: nil, operator: '=')
60
+ service = CollectionSpace::Service.get(type: type, subtype: subtype)
61
+ field ||= service[:term] # this will be set if it is an authority or vocabulary, otherwise nil
62
+ field ||= service[:identifier]
63
+ search_args = CollectionSpace::Search.new.from_hash(
64
+ path: service[:path],
65
+ namespace: "#{service[:ns_prefix]}_#{schema}",
66
+ field: field,
67
+ expression: "#{operator} '#{value.gsub(/'/, '\\\\\'')}'"
68
+ )
69
+ search(search_args, sortBy: CollectionSpace::Search::DEFAULT_SORT)
70
+ end
71
+ # rubocop:enable Metrics/ParameterLists
72
+
73
+ # @param subject_csid [String] to be searched as `sbj` value
74
+ # @param object_csid [String] to be searched as `obj` value
75
+ # @param rel_type [String<'affects', 'hasBroader'>, nil] to be searched as `prd` value
76
+ def find_relation(subject_csid:, object_csid:, rel_type: nil)
77
+ if rel_type
78
+ get('relations', query: { 'sbj' => subject_csid, 'obj' => object_csid, 'prd' => rel_type })
79
+ else
80
+ warn(
81
+ "No rel_type specified, so multiple types of relations between #{subject_csid} and #{object_csid} may be returned",
82
+ uplevel: 1
83
+ )
84
+ get('relations', query: { 'sbj' => subject_csid, 'obj' => object_csid })
85
+ end
86
+ end
101
87
 
102
88
  def get_list_types(path)
103
- list_type, list_item = nil
104
- if path == 'relations'
105
- list_type = 'relations_common_list'
106
- list_item = 'relation_list_item'
89
+ {
90
+ 'accounts' => %w[accounts_common_list account_list_item],
91
+ 'relations' => %w[relations_common_list relation_list_item]
92
+ }.fetch(path, %w[abstract_common_list list_item])
93
+ end
94
+
95
+ def reindex_full_text(doctype, csids = [])
96
+ if csids.any?
97
+ run_job(
98
+ 'Reindex Full Text', :reindex_full_text, :reindex_by_csids, { doctype: doctype, csids: csids }
99
+ )
107
100
  else
108
- list_type = 'abstract_common_list'
109
- list_item = 'list_item'
101
+ run_job(
102
+ 'Reindex Full Text', :reindex_full_text, :reindex_by_doctype, { doctype: doctype }
103
+ )
110
104
  end
111
- return list_type, list_item
112
105
  end
113
106
 
114
- def prepare_query(query, options = {})
115
- query_string = "#{query.type}:#{query.field} #{query.expression}"
116
- options = options.merge({ query: { as: query_string } })
117
- options
107
+ def reset_media_blob(id, url)
108
+ raise CollectionSpace::ArgumentError, "Not a valid url #{url}" unless URI.parse(url).instance_of? URI::HTTPS
109
+
110
+ response = find(type: 'media', value: id, field: 'identificationNumber')
111
+ raise CollectionSpace::RequestError, response.result.body unless response.result.success?
112
+
113
+ found = response.parsed
114
+ total = found['abstract_common_list']['totalItems'].to_i
115
+ raise CollectionSpace::NotFoundError, "Media #{id} not found" if total.zero?
116
+ raise CollectionSpace::DuplicateIdFound, "Found multiple media records for #{id}" unless total == 1
117
+
118
+ media_uri = found['abstract_common_list']['list_item']['uri']
119
+ blob_csid = found['abstract_common_list']['list_item']['blobCsid']
120
+
121
+ delete("/blobs/#{blob_csid}") if blob_csid
122
+
123
+ payload = Template.process(:reset_media_blob, { id: id })
124
+ put(media_uri, payload, query: { 'blobUri' => url })
118
125
  end
119
126
 
120
- end
127
+ def run_job(name, template, invoke_template, data = {})
128
+ payload = Template.process(invoke_template, data)
129
+ job = add_batch_job(name, template)
130
+ path = job.parsed['document']['collectionspace_core']['uri']
131
+ post(path, payload)
132
+ end
133
+
134
+ def search(query, params = {})
135
+ options = prepare_query(query, params)
136
+ request 'GET', query.path, options
137
+ end
138
+
139
+ def keyword_search(type:, value:, subtype: nil, sort: nil)
140
+ service = CollectionSpace::Service.get(type: type, subtype: subtype)
141
+ options = prepare_keyword_query(value, { sortBy: CollectionSpace::Search::DEFAULT_SORT })
142
+ request 'GET', service[:path], options
143
+ end
121
144
 
122
- end
145
+ def service(type:, subtype: '')
146
+ CollectionSpace::Service.get(type: type, subtype: subtype)
147
+ end
148
+
149
+ private
150
+
151
+ def create_or_update(response, path, property, value, payload)
152
+ list_type, item_type = get_list_types(path)
153
+ item = response.find(list_type, item_type, property, value)
154
+ path = item ? "#{path}/#{item['csid']}" : path
155
+ item ? put(path, payload) : post(path, payload)
156
+ end
157
+
158
+ def prepare_query(query, params = {})
159
+ query_string = "#{query.namespace}:#{query.field} #{query.expression}"
160
+ { query: { as: query_string }.merge(params) }
161
+ end
162
+
163
+ def prepare_keyword_query(query, sort = {})
164
+ query_string = query.downcase.gsub(' ', '+')
165
+ { query: { kw: query_string }.merge(sort) }
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+
5
+ module CollectionSpace
6
+ # CollectionSpace RefName
7
+ #
8
+ # There are four patterns we need to handle:
9
+ #
10
+ # - urn:cspace:domain:type:name(subtype)'label' : Top level authority/vocabulary
11
+ # - urn:cspace:domain:type:name(subtype):item:name(identifier)'label' : Authority/vocabulary term
12
+ # - urn:cspace:domain:type:id(identifier)'label' : Collectionobject
13
+ # - urn:cspace:domain:type:id(identifier) : Procedures, relations, blobs
14
+ class RefName
15
+ attr_reader :domain, :type, :subtype, :identifier, :label
16
+
17
+ def initialize(refname)
18
+ @refname = refname
19
+ @domain = nil
20
+ @type = nil
21
+ @subtype = nil
22
+ @identifier = nil
23
+ @label = nil
24
+ parse
25
+ end
26
+
27
+ def parse
28
+ scanner = StringScanner.new(@refname)
29
+ scanner.skip('urn:cspace:')
30
+ @domain = to_next_colon(scanner)
31
+ @type = to_next_colon(scanner)
32
+
33
+ case next_segment(scanner)
34
+ when 'name'
35
+ set_subtype(scanner)
36
+ when 'id'
37
+ set_identifier(scanner)
38
+ end
39
+
40
+ self
41
+ end
42
+
43
+ # Convenience class method, so new instance of RefName does not have to be instantiated in order to parse
44
+ #
45
+ # As of v0.13.1, return_class is added and defaults to nil for backward compatibility
46
+ # Eventually this default will be deprecated, and a parsed RefName object will be returned as the default.
47
+ # Any new code written using this method should set the return_class parameter to :refname_obj
48
+ def self.parse(refname, return_class = nil)
49
+ return_class == :refname_obj ? new(refname) : new(refname).to_h
50
+ end
51
+
52
+ # Returns a parsed RefName object as a hash.
53
+ # As of v0.13.1, this is equivalent to calling RefName.parse('refnamevalue', :hash)
54
+ # This was added to simplify the process of updating existing code that expects a hash when calling RefName.parse
55
+ def to_h
56
+ {
57
+ domain: domain,
58
+ type: type,
59
+ subtype: subtype,
60
+ identifier: identifier,
61
+ label: label
62
+ }
63
+ end
64
+
65
+ private
66
+
67
+ def next_segment(scanner)
68
+ segment = scanner.check_until(/\(/)
69
+ return nil unless segment
70
+
71
+ segment.delete_suffix('(')
72
+ end
73
+
74
+ def set_identifier(scanner)
75
+ scanner.skip('id(')
76
+ @identifier = to_end_paren(scanner)
77
+ return if scanner.eos?
78
+
79
+ set_label(scanner)
80
+ end
81
+
82
+ def set_label(scanner)
83
+ scanner.skip("'")
84
+ @label = scanner.rest.delete_suffix("'")
85
+ end
86
+
87
+ def set_subtype(scanner)
88
+ scanner.skip('name(')
89
+ @subtype = to_end_paren(scanner)
90
+
91
+ case next_segment(scanner)
92
+ when nil
93
+ set_label(scanner)
94
+ when ':item:name'
95
+ set_term_identifier(scanner)
96
+ end
97
+ end
98
+
99
+ def set_term_identifier(scanner)
100
+ scanner.skip(':item:name(')
101
+ @identifier = to_end_paren(scanner)
102
+ scanner.skip("'")
103
+ set_label(scanner)
104
+ end
105
+
106
+ def to_end_paren(scanner)
107
+ scanner.scan_until(/\)/).delete_suffix(')')
108
+ end
109
+
110
+ def to_next_colon(scanner)
111
+ scanner.scan_until(/:/).delete_suffix(':')
112
+ end
113
+ end
114
+ end
@@ -1,5 +1,7 @@
1
- module CollectionSpace
1
+ # frozen_string_literal: true
2
2
 
3
+ module CollectionSpace
4
+ # CollectionSpace request
3
5
  class Request
4
6
  include HTTParty
5
7
  attr_reader :config, :headers, :method, :path, :options
@@ -9,41 +11,43 @@ module CollectionSpace
9
11
  delete: {},
10
12
  get: {},
11
13
  post: {
12
- "Content-Type" => "application/xml",
13
- "Content-Length" => "nnnn",
14
+ 'Content-Type' => 'application/xml',
15
+ 'Content-Length' => 'nnnn'
14
16
  },
15
17
  put: {
16
- "Content-Type" => "application/xml",
17
- "Content-Length" => "nnnn",
18
+ 'Content-Type' => 'application/xml',
19
+ 'Content-Length' => 'nnnn'
18
20
  }
19
21
  }
20
22
  headers[method]
21
23
  end
22
24
 
23
- def initialize(config, method = "GET", path = "", options = {})
25
+ def initialize(config, method = 'GET', path = '', options = {})
24
26
  @config = config
25
27
  @method = method.downcase.to_sym
26
- @path = path.gsub(/^\//, '')
28
+ @path = path.gsub(%r{^/}, '')
27
29
 
28
30
  @auth = {
29
31
  username: config.username,
30
- password: config.password,
32
+ password: config.password
31
33
  }
32
34
 
35
+ headers = default_headers(@method).merge(options.fetch(:headers, {}))
33
36
  @options = options
34
37
  @options[:basic_auth] = @auth
35
- @options[:headers] = options[:headers] ? default_headers(@method).merge(options[:headers]) : default_headers(@method)
38
+ @options[:headers] = headers
36
39
  @options[:verify] = config.verify_ssl
37
- @options[:query] = {} unless options.has_key? :query
40
+ @options[:query] = options.fetch(:query, {})
38
41
 
39
42
  self.class.base_uri config.base_uri
40
- self.class.default_params wf_deleted: config.include_deleted, pgSz: config.page_size
43
+ self.class.default_params(
44
+ wf_deleted: config.include_deleted,
45
+ pgSz: config.page_size
46
+ )
41
47
  end
42
48
 
43
49
  def execute
44
50
  self.class.send method, "/#{path}", options
45
51
  end
46
-
47
52
  end
48
-
49
53
  end