collectionspace-client 0.1.5 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
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