commonmeta-ruby 3.2.15 → 3.3.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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/bin/commonmeta +1 -1
  4. data/lib/commonmeta/author_utils.rb +1 -1
  5. data/lib/commonmeta/cli.rb +17 -0
  6. data/lib/commonmeta/crossref_utils.rb +56 -14
  7. data/lib/commonmeta/readers/json_feed_reader.rb +25 -1
  8. data/lib/commonmeta/utils.rb +37 -0
  9. data/lib/commonmeta/version.rb +1 -1
  10. data/spec/cli_spec.rb +27 -3
  11. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/doi_prefix/doi_prefix_by_blog.yml +997 -0
  12. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/doi_prefix/doi_prefix_by_uuid.yml +256 -0
  13. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/encode/by_blog.yml +997 -0
  14. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/encode/by_blog_unknown_blog_id.yml +49 -0
  15. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/encode/by_uuid.yml +256 -0
  16. data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/encode/by_uuid_unknown_uuid.yml +49 -0
  17. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_doi_prefix_for_blog/by_blog_id.yml +997 -0
  18. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_doi_prefix_for_blog/by_blog_post_uuid.yml +389 -0
  19. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_doi_prefix_for_blog/by_blog_post_uuid_specific_prefix.yml +389 -0
  20. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_json_feed_item/by_uuid.yml +136 -0
  21. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_json_feed_item_metadata/blog_post_with_non-url_id.yml +136 -0
  22. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_json_feed_item_metadata/ghost_post_with_organizational_author.yml +91 -0
  23. data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/write_metadata_as_crossref/json_feed_item_from_rogue_scholar_with_organizational_author.yml +91 -0
  24. data/spec/readers/json_feed_reader_spec.rb +68 -0
  25. data/spec/utils_spec.rb +8 -0
  26. data/spec/writers/crossref_xml_writer_spec.rb +28 -0
  27. metadata +15 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43a78bfc4b2f9b77c966597e4593cdfef5f6b3fffc493857b99e224ff94fb70b
4
- data.tar.gz: 7b95a16aa8de6c5f7ce04352966e0855be5a174175514f77fe9ac82efc85d605
3
+ metadata.gz: 1a5ad35cfa39e4b1aadd232585375001d87f0a14c5f5638b6574c6982bb130a6
4
+ data.tar.gz: d7104f032539d68ee797c35e6f86d69fd8514691babec286ab3024fa49bac0cf
5
5
  SHA512:
6
- metadata.gz: cf12683197ddff178718725b72976b1b2fecef23c2bafff50f9c81b9c29b2da67554937830d60b9860d59d9294d5cd7ccd0e86d15633948758896abf3a8827c5
7
- data.tar.gz: e357c0d3871e0fd53e2371e792c487f2ba8895bb789982bddef496830d5aea08f0c571f209d8bcaf0f99365ec245eaf38621d7ad4230a548ab3e6f6ca1a7d7fe
6
+ metadata.gz: 86c9391a579504409de79cd3a2d449a7725a0b46014cde46e5ef5506b51e0595f5aa4a2404ac49fca20c8d8673b8b895234127e42ddf9314dd52214a7bdb9973
7
+ data.tar.gz: 26d1153f74db838c65c99b5229f2031c0ddb90b907fc3f7f51210bd177cf78e8417814437284378e38a86fb829bf36ccb1c6d5b3a7ac6d35678d870fac918e93
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- commonmeta-ruby (3.2.15)
4
+ commonmeta-ruby (3.3.1)
5
5
  activesupport (>= 4.2.5, < 8.0)
6
6
  addressable (~> 2.8.1, < 2.8.2)
7
7
  base32-url (>= 0.7.0, < 1)
data/bin/commonmeta CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require File.expand_path("../../lib/commonmeta", __FILE__)
4
4
 
5
- if (ARGV & %w(--version -v help --help encode decode encode_id decode_id json_feed_not_indexed json_feed_unregistered json_feed_by_blog)).empty?
5
+ if (ARGV & %w(--version -v help --help encode decode encode_id decode_id encode_by_blog encode_by_uuid json_feed_not_indexed json_feed_unregistered json_feed_by_blog)).empty?
6
6
  Commonmeta::CLI.start(ARGV.dup.unshift("convert"))
7
7
  else
8
8
  Commonmeta::CLI.start
@@ -41,7 +41,7 @@ module Commonmeta
41
41
  elsif id.nil? && author['ORCID'].present?
42
42
  id = author.fetch('ORCID')
43
43
  end
44
- id = normalize_orcid(id)
44
+ id = normalize_orcid(id) || normalize_ror(id)
45
45
 
46
46
  # parse author type, i.e. "Person", "Organization" or not specified
47
47
  type = author.fetch('type', nil)
@@ -59,6 +59,7 @@ module Commonmeta
59
59
  desc "", "encode"
60
60
 
61
61
  def encode(prefix)
62
+ return nil unless prefix.present?
62
63
  puts encode_doi(prefix)
63
64
  end
64
65
 
@@ -68,6 +69,22 @@ module Commonmeta
68
69
  puts encode_container_id
69
70
  end
70
71
 
72
+ desc "", "encode_by_blog"
73
+
74
+ def encode_by_blog(blog_id)
75
+ prefix = get_doi_prefix_by_blog_id(blog_id)
76
+ return nil unless prefix.present?
77
+ puts encode_doi(prefix)
78
+ end
79
+
80
+ desc "", "encode_by_uuid"
81
+
82
+ def encode_by_uuid(uuid)
83
+ prefix = get_doi_prefix_by_json_feed_item_uuid(uuid)
84
+ return nil unless prefix.present?
85
+ puts encode_doi(prefix)
86
+ end
87
+
71
88
  desc "", "decode"
72
89
 
73
90
  def decode(doi)
@@ -94,25 +94,67 @@ module Commonmeta
94
94
 
95
95
  def insert_crossref_creators(xml)
96
96
  xml.contributors do
97
- Array.wrap(creators).each_with_index do |person, index|
98
- xml.person_name("contributor_role" => "author",
99
- "sequence" => index.zero? ? "first" : "additional") do
100
- insert_crossref_person(xml, person)
97
+ Array.wrap(creators).each_with_index do |creator, index|
98
+ if creator["type"] == "Organization"
99
+ xml.organization("contributor_role" => "author",
100
+ "sequence" => index.zero? ? "first" : "additional") do
101
+ insert_crossref_organization(xml, creator)
102
+ end
103
+ elsif creator["givenName"].present? || creator["familyName"].present?
104
+ xml.person_name("contributor_role" => "author",
105
+ "sequence" => index.zero? ? "first" : "additional") do
106
+ insert_crossref_person(xml, creator)
107
+ end
108
+ else
109
+ xml.unknown("contributor_role" => "author",
110
+ "sequence" => index.zero? ? "first" : "additional") do
111
+ insert_crossref_anonymous(xml, creator)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def insert_crossref_person(xml, creator)
119
+ xml.given_name(creator["givenName"]) if creator["givenName"].present?
120
+ xml.surname(creator["familyName"]) if creator["familyName"].present?
121
+ if creator.dig("id") && URI.parse(creator.dig("id")).host == "orcid.org"
122
+ xml.ORCID(creator.dig("id"))
123
+ end
124
+ if creator["affiliation"].present?
125
+ xml.affiliations do
126
+ xml.institution do
127
+ xml.institution_name(creator.dig("affiliation", 0, "name")) if creator.dig("affiliation", 0, "name").present?
128
+ xml.institution_id(creator.dig("affiliation", 0, "affiliationIdentifier"), "type" => creator.dig("affiliation", 0, "affiliationIdentifierScheme")) if creator.dig("affiliation", 0, "affiliationIdentifier").present?
101
129
  end
102
130
  end
103
131
  end
104
132
  end
105
133
 
106
- def insert_crossref_person(xml, person)
107
- xml.given_name(person["givenName"]) if person["givenName"].present?
108
- xml.surname(person["familyName"]) if person["familyName"].present?
109
- if person.dig("id") && URI.parse(person.dig("id")).host == "orcid.org"
110
- xml.ORCID(person.dig("id"))
134
+ def insert_crossref_organization(xml, creator)
135
+ xml.name(creator["name"]) if creator["name"].present?
136
+ if creator["affiliation"].present?
137
+ xml.affiliations do
138
+ xml.institution do
139
+ xml.institution_name(creator.dig("affiliation", 0, "name")) if creator.dig("affiliation", 0, "name").present?
140
+ xml.institution_id(creator.dig("affiliation", 0, "affiliationIdentifier"), "type" => creator.dig("affiliation", 0, "affiliationIdentifierScheme")) if creator.dig("affiliation", 0, "affiliationIdentifier").present?
141
+ end
142
+ end
111
143
  end
112
- Array.wrap(person["affiliation"]).each do |affiliation|
113
- attributes = { "affiliationIdentifier" => affiliation["affiliationIdentifier"],
114
- "affiliationIdentifierScheme" => affiliation["affiliationIdentifierScheme"], "schemeURI" => affiliation["schemeUri"] }.compact
115
- xml.affiliation(affiliation["name"], attributes)
144
+ end
145
+
146
+ def insert_crossref_anonymous(xml, creator)
147
+ if person["affiliation"].present?
148
+ xml.anonymous do
149
+ xml.affiliations do
150
+ xml.institution do
151
+ xml.institution_name(creator.dig("affiliation", 0, "name")) if creator.dig("affiliation", 0, "name").present?
152
+ xml.institution_id(creator.dig("affiliation", 0, "affiliationIdentifier"), "type" => creator.dig("affiliation", 0, "affiliationIdentifierScheme")) if creator.dig("affiliation", 0, "affiliationIdentifier").present?
153
+ end
154
+ end
155
+ end
156
+ else
157
+ xml.anonymous
116
158
  end
117
159
  end
118
160
 
@@ -265,7 +307,7 @@ module Commonmeta
265
307
  }.compact
266
308
 
267
309
  # strip hyphen from UUIDs, as item_number can only be 32 characters long (UUIDv4 is 36 characters long)
268
- alternate_identifier["alternateIdentifier"] = alternate_identifier["alternateIdentifier"].gsub('-','') if alternate_identifier["alternateIdentifierType"] == "UUID"
310
+ alternate_identifier["alternateIdentifier"] = alternate_identifier["alternateIdentifier"].gsub("-", "") if alternate_identifier["alternateIdentifierType"] == "UUID"
269
311
 
270
312
  xml.item_number(alternate_identifier["alternateIdentifier"], attributes)
271
313
  end
@@ -20,8 +20,10 @@ module Commonmeta
20
20
 
21
21
  meta = string.present? ? JSON.parse(string) : {}
22
22
 
23
- id = options[:doi] ? normalize_doi(options[:doi]) : normalize_id(meta.fetch("id", nil))
24
23
  url = normalize_url(meta.fetch("url", nil))
24
+ id = options[:doi] ? normalize_doi(options[:doi]) : normalize_id(meta.fetch("id", nil))
25
+ id = url if id.blank? && url.present?
26
+
25
27
  type = "Article"
26
28
  creators = if meta.fetch("authors", nil).present?
27
29
  get_authors(from_json_feed(Array.wrap(meta.fetch("authors"))))
@@ -124,6 +126,28 @@ module Commonmeta
124
126
  blog = JSON.parse(response.body.to_s)
125
127
  blog["items"].map { |item| item["uuid"] }.first
126
128
  end
129
+
130
+ def get_doi_prefix_by_blog_id(blog_id)
131
+ # for generating a random DOI.
132
+
133
+ url = json_feed_by_blog_url(blog_id)
134
+ response = HTTP.get(url)
135
+ return nil unless response.status.success?
136
+
137
+ post = JSON.parse(response.body.to_s)
138
+ post.to_h.dig('prefix')
139
+ end
140
+
141
+ def get_doi_prefix_by_json_feed_item_uuid(uuid)
142
+ # for generating a random DOI. Prefix is based on the blog id.
143
+
144
+ url = json_feed_item_by_uuid_url(uuid)
145
+ response = HTTP.get(url)
146
+ return nil unless response.status.success?
147
+
148
+ post = JSON.parse(response.body.to_s)
149
+ post.to_h.dig('blog', 'prefix')
150
+ end
127
151
  end
128
152
  end
129
153
  end
@@ -543,6 +543,11 @@ module Commonmeta
543
543
  orcid.gsub(/[[:space:]]/, "-") if orcid.present?
544
544
  end
545
545
 
546
+ def validate_ror(ror)
547
+ ror = Array(%r{\A(?:(?:http|https)://ror\.org/)?([0-9a-z]{7}\d{2})\z}.match(ror)).last
548
+ ror.gsub(/[[:space:]]/, "-") if ror.present?
549
+ end
550
+
546
551
  def validate_orcid_scheme(orcid_scheme)
547
552
  Array(%r{\A(http|https)://(www\.)?(orcid\.org)}.match(orcid_scheme)).last
548
553
  end
@@ -634,6 +639,14 @@ module Commonmeta
634
639
  "https://orcid.org/" + Addressable::URI.encode(orcid)
635
640
  end
636
641
 
642
+ def normalize_ror(ror)
643
+ ror = validate_ror(ror)
644
+ return nil unless ror.present?
645
+
646
+ # turn ROR ID into URL
647
+ "https://ror.org/" + Addressable::URI.encode(ror)
648
+ end
649
+
637
650
  # pick electronic issn if there are multiple
638
651
  # format issn as xxxx-xxxx
639
652
  def normalize_issn(input, options = {})
@@ -1371,6 +1384,26 @@ module Commonmeta
1371
1384
  end
1372
1385
 
1373
1386
  def encode_doi(prefix, options = {})
1387
+ return nil unless prefix.present?
1388
+
1389
+ # DOI suffix is a generated from a random number, encoded in base32
1390
+ # suffix has 8 digits plus two checksum digits. With base32 there are
1391
+ # 32 possible digits, so 8 digits gives 32^8 possible combinations
1392
+ if options[:uuid]
1393
+ str = Base32::URL.encode_uuid(options[:uuid], split: 7, checksum: true)
1394
+ return nil unless str.present?
1395
+ else
1396
+ random_int = SecureRandom.random_number(32 ** 7..(32 ** 8) - 1)
1397
+ suffix = Base32::URL.encode(random_int, checksum: true)
1398
+ str = "#{suffix[0, 5]}-#{suffix[5, 10]}"
1399
+ end
1400
+ "https://doi.org/#{prefix}/#{str}"
1401
+ end
1402
+
1403
+ def encode_doi_for_uuid(uuid, options = {})
1404
+ # look up prefix for rogue scholar blog associated with uuid
1405
+ # returns nil if unknown uuid or doi registration is not enabled for blog
1406
+ json_feed_by_uuid(uuid)
1374
1407
  # DOI suffix is a generated from a random number, encoded in base32
1375
1408
  # suffix has 8 digits plus two checksum digits. With base32 there are
1376
1409
  # 32 possible digits, so 8 digits gives 32^8 possible combinations
@@ -1415,5 +1448,9 @@ module Commonmeta
1415
1448
  def json_feed_by_blog_url(blog_id)
1416
1449
  "https://rogue-scholar.org/api/blogs/#{blog_id}"
1417
1450
  end
1451
+
1452
+ def json_feed_item_by_uuid_url(uuid)
1453
+ "https://rogue-scholar.org/api/posts/#{uuid}"
1454
+ end
1418
1455
  end
1419
1456
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Commonmeta
4
- VERSION = '3.2.15'
4
+ VERSION = '3.3.1'
5
5
  end
data/spec/cli_spec.rb CHANGED
@@ -311,12 +311,36 @@ describe Commonmeta::CLI do
311
311
  # end
312
312
  end
313
313
 
314
- describe "encode" do
315
- let(:input) { "10.53731" }
316
-
314
+ describe "encode", vcr: true do
317
315
  it "blog prefix" do
316
+ input = "10.53731"
318
317
  expect { subject.encode input }.to output(/https:\/\/doi.org\/10.53731/).to_stdout
319
318
  end
319
+
320
+ it "blog prefix missing" do
321
+ input = ""
322
+ expect { subject.encode input }.to output("").to_stdout
323
+ end
324
+
325
+ it "by_blog" do
326
+ input = "tyfqw20"
327
+ expect { subject.encode_by_blog input }.to output(/https:\/\/doi.org\/10.59350/).to_stdout
328
+ end
329
+
330
+ it "by_blog unknown blog_id" do
331
+ input = "tyfqw"
332
+ expect { subject.encode_by_blog input }.to output("").to_stdout
333
+ end
334
+
335
+ it "by_uuid" do
336
+ input = "2b22bbba-bcba-4072-94cc-3f88442fff88"
337
+ expect { subject.encode_by_uuid input }.to output(/https:\/\/doi.org\/10.54900/).to_stdout
338
+ end
339
+
340
+ it "by_uuid unknown uuid" do
341
+ input = "2b22bbba-bcba-4072-94cc-3f88442"
342
+ expect { subject.encode_by_uuid input }.to output("").to_stdout
343
+ end
320
344
  end
321
345
 
322
346
  describe "decode" do