commonmeta-ruby 3.2.15 → 3.3.1

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