sul_orcid_client 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b75e419476e1943481450cb767b3f580fb25a16c4ca96cde068cd1176000972
4
- data.tar.gz: 784a82cb26eaa7f27137bf108a47e4c5d9584588e8c9b98dcabd2902674d7fc1
3
+ metadata.gz: 94e61c08976837c4f5fc946beca72656f4443258eace1a0c22c0d40884cb6e10
4
+ data.tar.gz: 8186d27f99e87462626a57aaa858abd33b8746615fde51148a2a9a3db76b67b1
5
5
  SHA512:
6
- metadata.gz: 0e47f1d153ae2dd5afcc676daf545f541d3e4c05ca9e89b0233f8ef1ef9bab896bbc6db6cb8a93c3f311de2fcecf2a48cfeb24cd7eb96e68ac2c587af742401c
7
- data.tar.gz: e7390bd10097109537407e1ce9550a58e85213a066b8bf41280e6bf8c23c23c00d7b1e27953ae17f096f6da3aa0c365cb6a62d9e2cba59bf906983691f29357b
6
+ metadata.gz: 1a814db636b2df5ee2294fac3e1303b63d43a868ad4a044a93f9c27e7f7b300a8eeb00b596cc10d6eefa6ac10433a7ceb989c16fccb92b0f3c9f00ec986af979
7
+ data.tar.gz: eebc8350317753b68e6cd0d748f90710f4cf7bf50d716d97cadb7021faa1484884d5e6970c62a8b9da025ecee955539152a1908d6a2397273f89af9cc052d500
data/.rubocop/custom.yml CHANGED
@@ -53,5 +53,33 @@ RSpec/MultipleExpectations:
53
53
  Enabled: false
54
54
  RSpec/ExampleLength:
55
55
  Enabled: false
56
-
57
-
56
+ Capybara/MatchStyle: # new in 2.17
57
+ Enabled: true
58
+ FactoryBot/AssociationStyle: # new in 2.23
59
+ Enabled: true
60
+ FactoryBot/FactoryAssociationWithStrategy: # new in 2.23
61
+ Enabled: true
62
+ FactoryBot/FactoryNameStyle: # new in 2.16
63
+ Enabled: true
64
+ FactoryBot/RedundantFactoryOption: # new in 2.23
65
+ Enabled: true
66
+ RSpec/BeEmpty: # new in 2.20
67
+ Enabled: true
68
+ RSpec/ContainExactly: # new in 2.19
69
+ Enabled: true
70
+ RSpec/DuplicatedMetadata: # new in 2.16
71
+ Enabled: true
72
+ RSpec/IndexedLet: # new in 2.20
73
+ Enabled: true
74
+ RSpec/MatchArray: # new in 2.19
75
+ Enabled: true
76
+ RSpec/PendingWithoutReason: # new in 2.16
77
+ Enabled: true
78
+ RSpec/RedundantAround: # new in 2.19
79
+ Enabled: true
80
+ RSpec/SkipBlockInsideExample: # new in 2.19
81
+ Enabled: true
82
+ RSpec/Rails/MinitestAssertions: # new in 2.17
83
+ Enabled: true
84
+ RSpec/Rails/TravelAround: # new in 2.19
85
+ Enabled: true
data/.rubocop.yml CHANGED
@@ -4,6 +4,8 @@ inherit_mode:
4
4
 
5
5
  require:
6
6
  - standard
7
+ - standard-custom
8
+ - standard-performance
7
9
  - rubocop-performance
8
10
  - rubocop-rspec
9
11
 
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sul_orcid_client (0.1.0)
4
+ sul_orcid_client (0.2.0)
5
5
  activesupport (>= 4.2, < 8)
6
+ cocina-models (~> 0.90)
6
7
  faraday
7
8
  faraday-retry
8
9
  oauth2
@@ -19,12 +20,54 @@ GEM
19
20
  addressable (2.8.4)
20
21
  public_suffix (>= 2.0.2, < 6.0)
21
22
  ast (2.4.2)
23
+ attr_extras (7.1.0)
22
24
  byebug (11.1.3)
25
+ cocina-models (0.90.0)
26
+ activesupport
27
+ deprecation
28
+ dry-struct (~> 1.0)
29
+ dry-types (~> 1.1)
30
+ edtf
31
+ equivalent-xml
32
+ jsonpath
33
+ nokogiri
34
+ openapi3_parser
35
+ openapi_parser (>= 0.11.1, < 1.0)
36
+ rss
37
+ super_diff
38
+ thor
39
+ zeitwerk (~> 2.1)
40
+ commonmarker (0.23.9)
23
41
  concurrent-ruby (1.2.2)
24
42
  crack (0.4.5)
25
43
  rexml
44
+ deprecation (1.1.0)
45
+ activesupport
26
46
  diff-lcs (1.5.0)
27
47
  docile (1.4.0)
48
+ dry-core (1.0.0)
49
+ concurrent-ruby (~> 1.0)
50
+ zeitwerk (~> 2.6)
51
+ dry-inflector (1.0.0)
52
+ dry-logic (1.5.0)
53
+ concurrent-ruby (~> 1.0)
54
+ dry-core (~> 1.0, < 2)
55
+ zeitwerk (~> 2.6)
56
+ dry-struct (1.6.0)
57
+ dry-core (~> 1.0, < 2)
58
+ dry-types (>= 1.7, < 2)
59
+ ice_nine (~> 0.11)
60
+ zeitwerk (~> 2.6)
61
+ dry-types (1.7.1)
62
+ concurrent-ruby (~> 1.0)
63
+ dry-core (~> 1.0)
64
+ dry-inflector (~> 1.0)
65
+ dry-logic (~> 1.4)
66
+ zeitwerk (~> 2.6)
67
+ edtf (3.1.1)
68
+ activesupport (>= 3.0, < 8.0)
69
+ equivalent-xml (0.6.0)
70
+ nokogiri (>= 1.4.3)
28
71
  faraday (2.7.10)
29
72
  faraday-net_http (>= 2.0, < 3.1)
30
73
  ruby2_keywords (>= 0.0.4)
@@ -35,12 +78,20 @@ GEM
35
78
  hashie (5.0.0)
36
79
  i18n (1.14.1)
37
80
  concurrent-ruby (~> 1.0)
81
+ ice_nine (0.11.2)
38
82
  json (2.6.3)
83
+ jsonpath (1.1.3)
84
+ multi_json
39
85
  jwt (2.7.1)
40
86
  language_server-protocol (3.17.0.3)
41
87
  lint_roller (1.1.0)
42
88
  minitest (5.18.1)
89
+ multi_json (1.15.0)
43
90
  multi_xml (0.6.0)
91
+ nokogiri (1.15.3-x86_64-darwin)
92
+ racc (~> 1.4)
93
+ nokogiri (1.15.3-x86_64-linux)
94
+ racc (~> 1.4)
44
95
  oauth2 (2.0.9)
45
96
  faraday (>= 0.17.3, < 3.0)
46
97
  jwt (>= 1.0, < 3.0)
@@ -48,10 +99,16 @@ GEM
48
99
  rack (>= 1.2, < 4)
49
100
  snaky_hash (~> 2.0)
50
101
  version_gem (~> 1.1)
102
+ openapi3_parser (0.9.2)
103
+ commonmarker (~> 0.17)
104
+ openapi_parser (0.15.0)
105
+ optimist (3.0.1)
51
106
  parallel (1.23.0)
52
107
  parser (3.2.2.3)
53
108
  ast (~> 2.4.1)
54
109
  racc
110
+ patience_diff (1.2.0)
111
+ optimist (~> 3.0)
55
112
  public_suffix (5.0.3)
56
113
  racc (1.7.1)
57
114
  rack (3.0.8)
@@ -72,6 +129,8 @@ GEM
72
129
  diff-lcs (>= 1.2.0, < 2.0)
73
130
  rspec-support (~> 3.12.0)
74
131
  rspec-support (3.12.1)
132
+ rss (0.2.9)
133
+ rexml
75
134
  rubocop (1.52.1)
76
135
  json (~> 2.3)
77
136
  parallel (~> 1.10)
@@ -106,17 +165,23 @@ GEM
106
165
  snaky_hash (2.0.1)
107
166
  hashie
108
167
  version_gem (~> 1.1, >= 1.1.1)
109
- standard (1.29.0)
168
+ standard (1.30.1)
110
169
  language_server-protocol (~> 3.17.0.2)
111
170
  lint_roller (~> 1.0)
112
171
  rubocop (~> 1.52.0)
113
172
  standard-custom (~> 1.0.0)
114
173
  standard-performance (~> 1.1.0)
115
- standard-custom (1.0.1)
174
+ standard-custom (1.0.2)
116
175
  lint_roller (~> 1.0)
176
+ rubocop (~> 1.50)
117
177
  standard-performance (1.1.1)
118
178
  lint_roller (~> 1.1)
119
179
  rubocop-performance (~> 1.18.0)
180
+ super_diff (0.10.0)
181
+ attr_extras (>= 6.2.4)
182
+ diff-lcs
183
+ patience_diff
184
+ thor (1.2.2)
120
185
  tzinfo (2.0.6)
121
186
  concurrent-ruby (~> 1.0)
122
187
  unicode-display_width (2.4.2)
@@ -138,7 +203,7 @@ DEPENDENCIES
138
203
  rspec (~> 3.0)
139
204
  rubocop-rspec
140
205
  simplecov
141
- standard (~> 1.29.0)
206
+ standard
142
207
  sul_orcid_client!
143
208
  vcr
144
209
  webmock
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/sul_orcid_client.svg)](https://badge.fury.io/rb/sul_orcid_client)
2
2
  [![CircleCI](https://dl.circleci.com/status-badge/img/gh/sul-dlss/orcid_client/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/sul-dlss/orcid_client/tree/main)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/5f53521299eeec2f9346/maintainability)](https://codeclimate.com/github/sul-dlss/orcid_client/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/5f53521299eeec2f9346/test_coverage)](https://codeclimate.com/github/sul-dlss/orcid_client/test_coverage)
3
5
 
4
- # sul_orcid_client
5
- API client for accessing ORCID API.
6
+ # orcid_client
7
+ API client for accessing ORCID API and for mapping to ORCID works.
8
+
9
+ Note: the *gem* name is `sul_orcid_client`; the git repo's name is `orcid_client`.
6
10
 
7
11
  ## Installation
8
12
 
@@ -0,0 +1,89 @@
1
+ class SulOrcidClient
2
+ # Maps a Cocina Contributor to an Orcid Contributor.
3
+
4
+ class ContributorMapper
5
+ def self.map(contributor:)
6
+ new(contributor: contributor).map
7
+ end
8
+
9
+ # @param [Cocina::Models::Contributor] contributor to map
10
+ def initialize(contributor:)
11
+ @contributor = contributor
12
+ end
13
+
14
+ def map
15
+ return unless cited?
16
+
17
+ {
18
+ "credit-name": map_credit_name,
19
+ "contributor-orcid": map_orcid,
20
+ "contributor-attributes": map_attributes
21
+ }.compact
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :contributor
27
+
28
+ def cited?
29
+ contributor.note.none? { |note| note.type == "citation status" && note.value == "false" }
30
+ end
31
+
32
+ def map_credit_name
33
+ value = if contributor.name.first&.structuredValue.present?
34
+ name_from_structured_value(contributor.name.first.structuredValue)
35
+ else
36
+ contributor.name.first&.value
37
+ end
38
+
39
+ return unless value
40
+
41
+ {
42
+ value: value
43
+ }
44
+ end
45
+
46
+ def name_from_structured_value(structured_value)
47
+ forename = structured_value.find { |name_part| name_part.type == "forename" }&.value
48
+ surname = structured_value.find { |name_part| name_part.type == "surname" }&.value
49
+ [forename, surname].join(" ")
50
+ end
51
+
52
+ def map_orcid
53
+ identifier = contributor.identifier.find { |identifier| ["ORCID", "ROR"].include?(identifier.type) }
54
+
55
+ return unless identifier
56
+
57
+ {
58
+ uri: URI.join(identifier.source.uri, identifier.value).to_s,
59
+ path: identifier.value,
60
+ host: (identifier.type == "ORCID") ? "orcid.org" : "ror.org"
61
+ }
62
+ end
63
+
64
+ def map_attributes
65
+ {
66
+ "contributor-role": map_role
67
+ }.compact.presence
68
+ end
69
+
70
+ MARC_RELATOR_MAP = {
71
+ "aut" => "Author",
72
+ "cmp" => "Author",
73
+ "ctb" => "Author",
74
+ "cre" => "Author",
75
+ "edt" => "Editor",
76
+ "rth" => "Principal investigator"
77
+ }
78
+
79
+ def map_role
80
+ role = contributor.role.find do |role|
81
+ role.source&.code == "marcrelator" && MARC_RELATOR_MAP.key?(role.code)
82
+ end
83
+
84
+ return unless role
85
+
86
+ MARC_RELATOR_MAP[role.code]
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SulOrcidClient
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,176 @@
1
+ class SulOrcidClient
2
+ # Maps a Cocina Description to an Orcid Work.
3
+
4
+ # Note that this mapping is currently based on a description generated
5
+ # from an H2 work. However, it could be extended to more completely map descriptions.
6
+ class WorkMapper
7
+ # Error raised by WorkMapper
8
+ class WorkMapperError < StandardError; end
9
+
10
+ def self.map(description:, doi: nil)
11
+ new(description: description, doi: doi).map
12
+ end
13
+
14
+ # @param [Cocina::Models::Description] description to map
15
+ # @param [String] doi from identification.doi
16
+ def initialize(description:, doi: nil)
17
+ @description = description
18
+ @doi = doi
19
+ end
20
+
21
+ def map
22
+ {
23
+ title: map_title,
24
+ "short-description": map_short_description,
25
+ citation: map_citation,
26
+ type: map_type,
27
+ "publication-date": map_publication_date,
28
+ "external-ids": map_external_ids,
29
+ url: description.purl,
30
+ contributors: map_contributors,
31
+ "language-code": "en",
32
+ country: {
33
+ value: "US"
34
+ }
35
+ }.compact
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :description, :doi
41
+
42
+ def map_title
43
+ title = description.title.first&.value
44
+ raise WorkMapperError, "Title not mapped" unless title
45
+
46
+ {
47
+ title: {
48
+ value: title.truncate(500) # ORCID has a max length for this field
49
+ }
50
+ }
51
+ end
52
+
53
+ def map_short_description
54
+ description.note.find { |note| note.type == "abstract" }&.value
55
+ end
56
+
57
+ def map_citation
58
+ citation = description.note.find { |note| note.type == "preferred citation" }&.value
59
+ return unless citation
60
+
61
+ {
62
+ "citation-type": "formatted-unspecified",
63
+ "citation-value": citation
64
+ }
65
+ end
66
+
67
+ def map_external_ids
68
+ {"external-id":
69
+ [
70
+ map_external_id("uri", description.purl, description.purl)
71
+ ].tap do |ids|
72
+ ids << map_external_id("doi", doi, "https://doi.org/#{doi}") if doi
73
+ end}
74
+ end
75
+
76
+ def map_external_id(type, value, url)
77
+ {
78
+ "external-id-type": type,
79
+ "external-id-value": value,
80
+ "external-id-url": {
81
+ value: url
82
+ },
83
+ "external-id-relationship": "self"
84
+ }
85
+ end
86
+
87
+ def map_publication_date
88
+ date = event_value("publication") || event_value("deposit")
89
+ return unless date
90
+
91
+ year, month, day = parse_date(date)
92
+ return unless year
93
+
94
+ {
95
+ year: {
96
+ value: year
97
+ }
98
+ }.tap do |publication_date|
99
+ publication_date[:month] = {value: month} if month
100
+ publication_date[:day] = {value: day} if month && day
101
+ end
102
+ end
103
+
104
+ def parse_date(date)
105
+ matcher = date.match(/(\d{4})-?(\d{2})?-?(\d{2})?/)
106
+ return [nil, nil, nil] unless matcher
107
+
108
+ matcher[1..3]
109
+ end
110
+
111
+ def event_value(type)
112
+ description&.event&.find { |event| event.type == type }&.date&.first&.value
113
+ end
114
+
115
+ H2_TERM_MAP = {
116
+ "Data" => "data-set",
117
+ "Software/Code" => "software",
118
+ "Article" => "journal-article",
119
+ "Book" => "book",
120
+ "Book chapter" => "book-chapter",
121
+ "Code" => "software",
122
+ "Conference session" => "lecture-speech",
123
+ "Course/instructional materials" => "manual",
124
+ "Database" => "data-set",
125
+ "Dramatic performance" => "artistic-performance",
126
+ "Geospatial data" => "data-set",
127
+ "Journal/periodical issue" => "journal-issue",
128
+ "Performance" => "artistic-performance",
129
+ "Poetry reading" => "artistic-performance",
130
+ "Poster" => "conference-poster",
131
+ "Preprint" => "preprint",
132
+ "Presentation recording" => "lecture-speech",
133
+ "Questionnaire" => "research-technique",
134
+ "Report" => "report",
135
+ "Software" => "software",
136
+ "Speech" => "lecture-speech",
137
+ "Statistical model" => "research-technique",
138
+ "Syllabus" => "manual",
139
+ "Tabular data" => "data-set",
140
+ "Technical report" => "report",
141
+ "Text corpus" => "data-set",
142
+ "Thesis" => "dissertation-thesis",
143
+ "Working paper" => "working-paper"
144
+ }
145
+
146
+ def map_type
147
+ # See https://info.orcid.org/ufaqs/what-work-types-does-orcid-support/
148
+ # For now, only mapping H2 terms; if there is not an H2 term, using "other".
149
+
150
+ h2_form = description.form.find { |form| form.source&.value == "Stanford self-deposit resource types" }
151
+ return "other" unless h2_form
152
+
153
+ map_h2_resource_types(h2_form)
154
+ end
155
+
156
+ def map_h2_resource_types(form)
157
+ # Try to match subtypes first, then type
158
+ subtype_terms = form.structuredValue.select { |term| term.type == "subtype" }.map(&:value)
159
+ type_term = form.structuredValue.find { |term| term.type == "type" }&.value
160
+ matching_term = (subtype_terms + [type_term]).find { |term| H2_TERM_MAP.key?(term) }
161
+ return "other" unless matching_term
162
+
163
+ H2_TERM_MAP[matching_term]
164
+ end
165
+
166
+ def map_contributors
167
+ contributors = description.contributor.map do |contributor|
168
+ ContributorMapper.map(contributor: contributor)
169
+ end.compact.presence
170
+ return unless contributors
171
+ {
172
+ contributor: contributors
173
+ }
174
+ end
175
+ end
176
+ end
@@ -33,7 +33,7 @@ class SulOrcidClient
33
33
  self
34
34
  end
35
35
 
36
- delegate :fetch_works, :fetch_work, :fetch_name, :search, :add_work, :delete_work, to: :instance
36
+ delegate :fetch_works, :fetch_work, :fetch_name, :search, :add_work, :update_work, :delete_work, to: :instance
37
37
  end
38
38
 
39
39
  attr_accessor :base_url, :base_public_url, :base_auth_url, :client_id, :client_secret
@@ -124,6 +124,22 @@ class SulOrcidClient
124
124
  end
125
125
  end
126
126
 
127
+ # Update an existing work for a researcher.
128
+ # @param [String] orcidid an ORCiD ID for the researcher
129
+ # @param [Hash] work a work in correct data structure for ORCID work
130
+ # @param [String] token an ORCiD API access token
131
+ # @param [String] put_code the PUT code
132
+ # @return [Boolean] true if update succeeded
133
+ # @raise [RuntimeError] if the API response status is not successful
134
+ def update_work(orcidid:, work:, token:, put_code:)
135
+ response = conn_with_token(token).put("/v3.0/#{base_orcidid(orcidid)}/work/#{put_code}",
136
+ work.merge({"put-code" => put_code}).to_json,
137
+ "Content-Type" => "application/vnd.orcid+json")
138
+
139
+ raise "ORCID.org API returned #{response.status} when updating #{put_code} for #{orcidid}" unless response.status == 200
140
+ true
141
+ end
142
+
127
143
  # Delete a work
128
144
  # @param [string] ORCID ID for the researcher
129
145
  # @param [string] put-code
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.require_paths = ["lib"]
33
33
 
34
34
  spec.add_dependency "activesupport", ">= 4.2", "< 8"
35
+ spec.add_dependency "cocina-models", "~> 0.90"
35
36
  spec.add_dependency "faraday"
36
37
  spec.add_dependency "faraday-retry"
37
38
  spec.add_dependency "oauth2"
@@ -41,7 +42,7 @@ Gem::Specification.new do |spec|
41
42
  spec.add_development_dependency "rspec", "~> 3.0"
42
43
  spec.add_development_dependency "rubocop-rspec"
43
44
  spec.add_development_dependency "simplecov"
44
- spec.add_development_dependency "standard", "~> 1.29.0" # Pinning until https://github.com/standardrb/standard-custom/pull/5
45
+ spec.add_development_dependency "standard"
45
46
  spec.add_development_dependency "vcr"
46
47
  spec.add_development_dependency "webmock"
47
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sul_orcid_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Mangiafico
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-13 00:00:00.000000000 Z
12
+ date: 2023-07-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,6 +31,20 @@ dependencies:
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '8'
34
+ - !ruby/object:Gem::Dependency
35
+ name: cocina-models
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.90'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.90'
34
48
  - !ruby/object:Gem::Dependency
35
49
  name: faraday
36
50
  requirement: !ruby/object:Gem::Requirement
@@ -147,16 +161,16 @@ dependencies:
147
161
  name: standard
148
162
  requirement: !ruby/object:Gem::Requirement
149
163
  requirements:
150
- - - "~>"
164
+ - - ">="
151
165
  - !ruby/object:Gem::Version
152
- version: 1.29.0
166
+ version: '0'
153
167
  type: :development
154
168
  prerelease: false
155
169
  version_requirements: !ruby/object:Gem::Requirement
156
170
  requirements:
157
- - - "~>"
171
+ - - ">="
158
172
  - !ruby/object:Gem::Version
159
- version: 1.29.0
173
+ version: '0'
160
174
  - !ruby/object:Gem::Dependency
161
175
  name: vcr
162
176
  requirement: !ruby/object:Gem::Requirement
@@ -203,7 +217,9 @@ files:
203
217
  - README.md
204
218
  - Rakefile
205
219
  - lib/sul_orcid_client.rb
220
+ - lib/sul_orcid_client/contributor_mapper.rb
206
221
  - lib/sul_orcid_client/version.rb
222
+ - lib/sul_orcid_client/work_mapper.rb
207
223
  - sul_orcid_client.gemspec
208
224
  homepage: https://github.com/sul-dlss/sul_orcid_client
209
225
  licenses: []