serrano 0.3.6 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +28 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +50 -0
- data/.rubocop_todo.yml +105 -0
- data/CHANGELOG.md +33 -0
- data/{CONDUCT.md → CODE_OF_CONDUCT.md} +2 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +56 -38
- data/LICENSE +1 -1
- data/README.md +35 -7
- data/Rakefile +26 -18
- data/bin/serrano +96 -98
- data/lib/serrano.rb +194 -77
- data/lib/serrano/cn.rb +7 -9
- data/lib/serrano/cnrequest.rb +42 -28
- data/lib/serrano/error.rb +2 -0
- data/lib/serrano/faraday.rb +15 -14
- data/lib/serrano/filterhandler.rb +13 -15
- data/lib/serrano/filters.rb +91 -78
- data/lib/serrano/helpers/configuration.rb +2 -2
- data/lib/serrano/request.rb +46 -39
- data/lib/serrano/request_cursor.rb +66 -59
- data/lib/serrano/styles.rb +14 -9
- data/lib/serrano/utils.rb +21 -14
- data/lib/serrano/version.rb +3 -1
- data/serrano.gemspec +33 -20
- metadata +87 -69
- data/.travis.yml +0 -8
- data/lib/serrano/constants.rb +0 -36
- data/lib/serrano/cursor_testing.rb +0 -52
data/Rakefile
CHANGED
@@ -1,41 +1,49 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
2
4
|
require 'rake/testtask'
|
5
|
+
require 'rubocop/rake_task'
|
3
6
|
|
4
7
|
Rake::TestTask.new do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.test_files = FileList['test/
|
8
|
+
t.libs << 'test'
|
9
|
+
t.test_files = FileList['test/test_*.rb']
|
7
10
|
t.verbose = true
|
11
|
+
t.warning = false
|
8
12
|
end
|
9
13
|
|
10
|
-
desc
|
11
|
-
task :
|
14
|
+
desc 'Run tests'
|
15
|
+
task default: :test
|
16
|
+
|
17
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
18
|
+
t.options = ['--display-cop-names']
|
19
|
+
end
|
12
20
|
|
13
|
-
desc
|
21
|
+
desc 'Build serrano docs'
|
14
22
|
task :docs do
|
15
|
-
|
23
|
+
system 'yardoc'
|
16
24
|
end
|
17
25
|
|
18
|
-
desc
|
26
|
+
desc 'bundle install'
|
19
27
|
task :bundle do
|
20
|
-
system
|
28
|
+
system 'bundle install'
|
21
29
|
end
|
22
30
|
|
23
|
-
desc
|
31
|
+
desc 'clean out builds'
|
24
32
|
task :clean do
|
25
|
-
system
|
33
|
+
system 'ls | grep [0-9].gem | xargs rm'
|
26
34
|
end
|
27
35
|
|
28
|
-
desc
|
36
|
+
desc 'Build serrano'
|
29
37
|
task :build do
|
30
|
-
|
38
|
+
system 'gem build serrano.gemspec'
|
31
39
|
end
|
32
40
|
|
33
|
-
desc
|
34
|
-
task :
|
35
|
-
|
41
|
+
desc 'Install serrano'
|
42
|
+
task install: %i[bundle build] do
|
43
|
+
system "gem install serrano-#{Serrano::VERSION}.gem"
|
36
44
|
end
|
37
45
|
|
38
|
-
desc
|
39
|
-
task :
|
46
|
+
desc 'Release to Rubygems'
|
47
|
+
task release: :build do
|
40
48
|
system "gem push serrano-#{Serrano::VERSION}.gem"
|
41
49
|
end
|
data/bin/serrano
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require 'thor'
|
5
|
+
require 'serrano'
|
6
|
+
require 'multi_json'
|
6
7
|
|
7
8
|
class Sr < Thor
|
8
9
|
include Thor::Actions
|
9
10
|
# class_option :json, :type => :boolean, :default => false
|
10
11
|
|
11
|
-
desc
|
12
|
+
desc 'works [DOIs]', 'Get works by DOIs'
|
12
13
|
long_desc <<-LONGDESC
|
13
14
|
`serrano works` accepts one or more DOIs to search for works
|
14
15
|
|
@@ -47,17 +48,17 @@ class Sr < Thor
|
|
47
48
|
\x5"http://orcid.org/0000-0003-4087-8021"
|
48
49
|
\x5"http://orcid.org/0000-0002-2076-5452"
|
49
50
|
LONGDESC
|
50
|
-
option :json, :
|
51
|
-
option :filter, :
|
52
|
-
option :limit, :
|
53
|
-
def works(ids=nil)
|
54
|
-
if ids.nil?
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
51
|
+
option :json, type: :boolean, default: false
|
52
|
+
option :filter, type: :hash, default: nil
|
53
|
+
option :limit, type: :numeric, default: nil
|
54
|
+
def works(ids = nil)
|
55
|
+
out = if ids.nil?
|
56
|
+
Serrano.works(filter: options[:filter], limit: options[:limit])
|
57
|
+
else
|
58
|
+
Serrano.works(ids: ids.split(','), filter: options[:filter])
|
59
|
+
end
|
59
60
|
if !options[:json]
|
60
|
-
out = out.collect { |x| x['message'].select { |k,
|
61
|
+
out = out.collect { |x| x['message'].select { |k, _v| k[/DOI|type|title/] } }
|
61
62
|
out.each do |x|
|
62
63
|
puts 'DOI: ' + x['DOI'].to_s
|
63
64
|
puts 'type: ' + x['type']
|
@@ -69,7 +70,7 @@ class Sr < Thor
|
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
72
|
-
desc
|
73
|
+
desc 'members [member IDs]', 'Get members by id'
|
73
74
|
long_desc <<-LONGDESC
|
74
75
|
`serrano members` accepts one or more Crossref member IDs
|
75
76
|
|
@@ -96,26 +97,26 @@ class Sr < Thor
|
|
96
97
|
|
97
98
|
24 7 45 31 22 67 72 17 53 75 0 68 12 16 50 79 40 18 40 195
|
98
99
|
LONGDESC
|
99
|
-
option :json, :
|
100
|
-
option :query, :
|
101
|
-
option :limit, :
|
102
|
-
option :works, :
|
103
|
-
def members(ids=nil)
|
100
|
+
option :json, type: :boolean, default: false
|
101
|
+
option :query, type: :string, default: nil
|
102
|
+
option :limit, type: :numeric, default: nil
|
103
|
+
option :works, type: :boolean, default: false
|
104
|
+
def members(ids = nil)
|
104
105
|
if ids.nil?
|
105
106
|
out = Serrano.members(query: options[:query], limit: options[:limit],
|
106
|
-
|
107
|
+
works: options[:works])
|
107
108
|
else
|
108
|
-
out = Serrano.members(ids: ids.split(
|
109
|
-
|
109
|
+
out = Serrano.members(ids: ids.split(','), query: options[:query], limit: options[:limit],
|
110
|
+
works: options[:works])
|
110
111
|
end
|
111
112
|
|
112
113
|
if !options[:json]
|
113
114
|
if !options[:works]
|
114
|
-
if out.class == Hash
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
out = if out.class == Hash
|
116
|
+
out['message']['items']
|
117
|
+
else
|
118
|
+
out.collect { |x| x['message'] }
|
119
|
+
end
|
119
120
|
|
120
121
|
# out = out.collect { |x| x['message'].select { |k,v| k[/id|primary-name/] } }
|
121
122
|
out.each do |x|
|
@@ -131,8 +132,7 @@ class Sr < Thor
|
|
131
132
|
end
|
132
133
|
end
|
133
134
|
|
134
|
-
|
135
|
-
desc "prefixes [DOI prefixes]", "Search for prefixes by DOI prefix"
|
135
|
+
desc 'prefixes [DOI prefixes]', 'Search for prefixes by DOI prefix'
|
136
136
|
long_desc <<-LONGDESC
|
137
137
|
`serrano prefixes` accepts one or more Crossref member IDs
|
138
138
|
|
@@ -162,24 +162,24 @@ class Sr < Thor
|
|
162
162
|
\x5"Co-Action Publishing"
|
163
163
|
\x5"Oxford University Press (OUP)"
|
164
164
|
LONGDESC
|
165
|
-
option :json, :
|
166
|
-
option :limit, :
|
167
|
-
option :works, :
|
168
|
-
def prefixes(ids=nil)
|
169
|
-
if ids.nil?
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
165
|
+
option :json, type: :boolean, default: false
|
166
|
+
option :limit, type: :numeric, default: nil
|
167
|
+
option :works, type: :boolean, default: false
|
168
|
+
def prefixes(ids = nil)
|
169
|
+
out = if ids.nil?
|
170
|
+
Serrano.prefixes(limit: options[:limit], works: options[:works])
|
171
|
+
else
|
172
|
+
Serrano.prefixes(ids: ids.split(','), limit: options[:limit],
|
173
|
+
works: options[:works])
|
174
|
+
end
|
175
175
|
|
176
176
|
if !options[:json]
|
177
177
|
if !options[:works]
|
178
|
-
if out.class == Hash
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
178
|
+
out = if out.class == Hash
|
179
|
+
out['message']['items']
|
180
|
+
else
|
181
|
+
out.collect { |x| x['message'] }
|
182
|
+
end
|
183
183
|
|
184
184
|
out.each do |x|
|
185
185
|
puts 'member: ' + x['member']
|
@@ -195,7 +195,7 @@ class Sr < Thor
|
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
198
|
-
desc
|
198
|
+
desc 'funders [funder IDs]', 'Search for funders by DOI prefix'
|
199
199
|
long_desc <<-LONGDESC
|
200
200
|
`serrano funders` accepts one or more Crossref funder IDs
|
201
201
|
|
@@ -226,26 +226,26 @@ class Sr < Thor
|
|
226
226
|
|
227
227
|
$ serrano funders 10.13039/100000001 --works=true --limit=2
|
228
228
|
LONGDESC
|
229
|
-
option :json, :
|
230
|
-
option :query, :
|
231
|
-
option :limit, :
|
232
|
-
option :works, :
|
233
|
-
def funders(ids=nil)
|
229
|
+
option :json, type: :boolean, default: false
|
230
|
+
option :query, type: :string, default: nil
|
231
|
+
option :limit, type: :numeric, default: nil
|
232
|
+
option :works, type: :boolean, default: false
|
233
|
+
def funders(ids = nil)
|
234
234
|
if ids.nil?
|
235
235
|
out = Serrano.funders(query: options[:query], limit: options[:limit],
|
236
|
-
|
236
|
+
works: options[:works])
|
237
237
|
else
|
238
|
-
out = Serrano.funders(ids: ids.split(
|
239
|
-
|
238
|
+
out = Serrano.funders(ids: ids.split(','), query: options[:query], limit: options[:limit],
|
239
|
+
works: options[:works])
|
240
240
|
end
|
241
241
|
|
242
242
|
if !options[:json]
|
243
243
|
if !options[:works]
|
244
|
-
if out.class == Hash
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
244
|
+
out = if out.class == Hash
|
245
|
+
out['message']['items']
|
246
|
+
else
|
247
|
+
out.collect { |x| x['message'] }
|
248
|
+
end
|
249
249
|
|
250
250
|
out.each do |x|
|
251
251
|
puts 'id: ' + x['id']
|
@@ -261,8 +261,7 @@ class Sr < Thor
|
|
261
261
|
end
|
262
262
|
end
|
263
263
|
|
264
|
-
|
265
|
-
desc "journals [journal ISSNs]", "Search for journals by ISSNs"
|
264
|
+
desc 'journals [journal ISSNs]', 'Search for journals by ISSNs'
|
266
265
|
long_desc <<-LONGDESC
|
267
266
|
`serrano journals` accepts one or more journal ISSNs
|
268
267
|
|
@@ -293,26 +292,26 @@ class Sr < Thor
|
|
293
292
|
|
294
293
|
$ serrano journals 2167-8359 --query=ecology --works=true --limit=2
|
295
294
|
LONGDESC
|
296
|
-
option :json, :
|
297
|
-
option :query, :
|
298
|
-
option :limit, :
|
299
|
-
option :works, :
|
300
|
-
def journals(ids=nil)
|
295
|
+
option :json, type: :boolean, default: false
|
296
|
+
option :query, type: :string, default: nil
|
297
|
+
option :limit, type: :numeric, default: nil
|
298
|
+
option :works, type: :boolean, default: false
|
299
|
+
def journals(ids = nil)
|
301
300
|
if ids.nil?
|
302
301
|
out = Serrano.journals(query: options[:query], limit: options[:limit],
|
303
|
-
|
302
|
+
works: options[:works])
|
304
303
|
else
|
305
|
-
out = Serrano.journals(ids: ids.split(
|
306
|
-
|
304
|
+
out = Serrano.journals(ids: ids.split(','), query: options[:query], limit: options[:limit],
|
305
|
+
works: options[:works])
|
307
306
|
end
|
308
307
|
|
309
308
|
if !options[:json]
|
310
309
|
if !options[:works]
|
311
|
-
if out.class == Hash
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
310
|
+
out = if out.class == Hash
|
311
|
+
out['message']['items']
|
312
|
+
else
|
313
|
+
out.collect { |x| x['message'] }
|
314
|
+
end
|
316
315
|
|
317
316
|
out.each do |x|
|
318
317
|
puts 'ISSN: ' + x['ISSN'][0]
|
@@ -328,7 +327,7 @@ class Sr < Thor
|
|
328
327
|
end
|
329
328
|
end
|
330
329
|
|
331
|
-
desc
|
330
|
+
desc 'types [type name]', 'Search for types by name'
|
332
331
|
long_desc <<-LONGDESC
|
333
332
|
`serrano types` accepts one or more type names
|
334
333
|
|
@@ -359,20 +358,20 @@ class Sr < Thor
|
|
359
358
|
|
360
359
|
$ serrano types dissertation --works --limit=2
|
361
360
|
LONGDESC
|
362
|
-
option :json, :
|
363
|
-
option :works, :
|
364
|
-
option :limit, :
|
361
|
+
option :json, type: :boolean, default: false
|
362
|
+
option :works, type: :boolean, default: false
|
363
|
+
option :limit, type: :numeric, default: nil
|
365
364
|
def types(*ids)
|
366
365
|
out = Serrano.types(ids: ids, limit: options[:limit],
|
367
|
-
|
366
|
+
works: options[:works])
|
368
367
|
|
369
368
|
if !options[:json]
|
370
369
|
if !options[:works]
|
371
|
-
if out.class == Hash
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
370
|
+
out = if out.class == Hash
|
371
|
+
out['message']['items']
|
372
|
+
else
|
373
|
+
out.collect { |x| x['message'] }
|
374
|
+
end
|
376
375
|
|
377
376
|
out.each do |x|
|
378
377
|
puts 'id: ' + x['id']
|
@@ -387,7 +386,7 @@ class Sr < Thor
|
|
387
386
|
end
|
388
387
|
end
|
389
388
|
|
390
|
-
desc
|
389
|
+
desc 'licenses', 'Search for licenses by name'
|
391
390
|
long_desc <<-LONGDESC
|
392
391
|
`serrano licenses` accepts one or more type names
|
393
392
|
|
@@ -416,8 +415,8 @@ class Sr < Thor
|
|
416
415
|
"National Science Foundation"
|
417
416
|
\x5"U.S. Department of Energy"
|
418
417
|
LONGDESC
|
419
|
-
option :json, :
|
420
|
-
option :query, :
|
418
|
+
option :json, type: :boolean, default: false
|
419
|
+
option :query, type: :string, default: nil
|
421
420
|
def licenses
|
422
421
|
out = Serrano.licenses(query: options[:query])
|
423
422
|
if !options[:json]
|
@@ -432,7 +431,7 @@ class Sr < Thor
|
|
432
431
|
end
|
433
432
|
end
|
434
433
|
|
435
|
-
desc
|
434
|
+
desc 'contneg', 'Content negotiation'
|
436
435
|
long_desc <<-LONGDESC
|
437
436
|
`serrano contneg` accepts a DOI
|
438
437
|
|
@@ -444,15 +443,15 @@ class Sr < Thor
|
|
444
443
|
|
445
444
|
Murtaugh PA (2014). In defense of P values . Ecology 95: 611–617.
|
446
445
|
LONGDESC
|
447
|
-
option :format, :
|
448
|
-
option :style, :
|
449
|
-
option :locale, :
|
446
|
+
option :format, type: :string, default: 'text'
|
447
|
+
option :style, type: :string, default: 'apa'
|
448
|
+
option :locale, type: :string, default: 'en-US'
|
450
449
|
def contneg(ids)
|
451
450
|
puts Serrano.content_negotiation(ids: ids, format: options[:format], style:
|
452
451
|
options[:style], locale: options[:locale])
|
453
452
|
end
|
454
453
|
|
455
|
-
desc
|
454
|
+
desc 'version', 'Get serrano version'
|
456
455
|
def version
|
457
456
|
puts Serrano::VERSION
|
458
457
|
end
|
@@ -460,11 +459,11 @@ class Sr < Thor
|
|
460
459
|
private
|
461
460
|
|
462
461
|
def print_works(data)
|
463
|
-
if data.is_a? Array
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
462
|
+
data = if data.is_a? Array
|
463
|
+
data[0]['message']['items']
|
464
|
+
else
|
465
|
+
data.collect { |x| x['message'].select { |k, _v| k[/DOI|type|title/] } }
|
466
|
+
end
|
468
467
|
data.each do |x|
|
469
468
|
puts 'DOI: ' + x['DOI']
|
470
469
|
puts 'type: ' + x['type']
|
@@ -472,7 +471,6 @@ class Sr < Thor
|
|
472
471
|
puts
|
473
472
|
end
|
474
473
|
end
|
475
|
-
|
476
474
|
end
|
477
475
|
|
478
476
|
Sr.start(ARGV)
|
data/lib/serrano.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'serrano/version'
|
5
|
+
require 'serrano/request'
|
6
|
+
require 'serrano/request_cursor'
|
7
|
+
require 'serrano/filterhandler'
|
8
|
+
require 'serrano/cnrequest'
|
9
|
+
require 'serrano/filters'
|
10
|
+
require 'serrano/styles'
|
8
11
|
|
9
12
|
require 'rexml/document'
|
10
13
|
require 'rexml/xpath'
|
@@ -18,13 +21,21 @@ require 'rexml/xpath'
|
|
18
21
|
# Max: 100.
|
19
22
|
# @param sort [String] Field to sort on, one of score, relevance,
|
20
23
|
# updated (date of most recent change to metadata - currently the same as deposited),
|
21
|
-
# deposited (time of most recent deposit), indexed (time of most recent index),
|
22
|
-
# published (publication date)
|
24
|
+
# deposited (time of most recent deposit), indexed (time of most recent index),
|
25
|
+
# published (publication date), published-print (print publication date),
|
26
|
+
# published-online (online publication date), issued (issued date (earliest known publication date)),
|
27
|
+
# is-referenced-by-count (number of times this DOI is referenced by other Crossref DOIs), or
|
28
|
+
# references-count (number of references included in the references section of the document
|
29
|
+
# identified by this DOI). Note: If the API call includes a query, then the sort
|
23
30
|
# order will be by the relevance score. If no query is included, then the sort order
|
24
31
|
# will be by DOI update date.
|
25
32
|
# @param order [String] Sort order, one of 'asc' or 'desc'
|
26
33
|
# @param facet [Boolean/String] Include facet results OR a query (e.g., `license:*`) to facet by
|
27
34
|
# license. Default: false
|
35
|
+
# @param select [String/Array(String)] Crossref metadata records can be
|
36
|
+
# quite large. Sometimes you just want a few elements from the schema. You can "select"
|
37
|
+
# a subset of elements to return. This can make your API calls much more efficient. Not
|
38
|
+
# clear yet which fields are allowed here.
|
28
39
|
# @param verbose [Boolean] Print request headers to stdout. Default: false
|
29
40
|
|
30
41
|
# @!macro cursor_params
|
@@ -52,10 +63,18 @@ require 'rexml/xpath'
|
|
52
63
|
# - oauth [Hash] A hash with OAuth details
|
53
64
|
|
54
65
|
# @!macro field_queries
|
55
|
-
# @param [Hash<Object>] args Field queries, as named parameters.
|
56
|
-
#
|
66
|
+
# @param [Hash<Object>] args Field queries, as named parameters. See
|
67
|
+
# https://github.com/CrossRef/rest-api-doc/blob/master/rest_api.md#field-queries
|
57
68
|
# Field query parameters mut be named, and must start with `query_`. Any dashes or
|
58
|
-
# periods should be replaced with underscores.
|
69
|
+
# periods should be replaced with underscores. The options include:
|
70
|
+
# - query_container_title: Query container-title aka. publication name
|
71
|
+
# - query_author: Query author given and family names
|
72
|
+
# - query_editor: Query editor given and family names
|
73
|
+
# - query_chair: Query chair given and family names
|
74
|
+
# - query_translator: Query translator given and family names
|
75
|
+
# - query_contributor: Query author, editor, chair and translator given and family names
|
76
|
+
# - query_bibliographic: Query bibliographic information, useful for citation look up. Includes titles, authors, ISSNs and publication years
|
77
|
+
# - query_affiliation: Query contributor affiliations
|
59
78
|
|
60
79
|
##
|
61
80
|
# Serrano - The top level module for using methods
|
@@ -91,7 +110,29 @@ require 'rexml/xpath'
|
|
91
110
|
# searching full text, or even abstracts of articles, but only what is
|
92
111
|
# available in the data that is returned to you. That is, they search
|
93
112
|
# article titles, authors, etc. For some discussion on this, see
|
94
|
-
# https://
|
113
|
+
# https://gitlab.com/crossref/issues/issues/101
|
114
|
+
#
|
115
|
+
#
|
116
|
+
# The Polite Pool
|
117
|
+
# As of September 18th 2017 any API queries that use HTTPS and have
|
118
|
+
# appropriate contact information will be directed to a special pool
|
119
|
+
# of API machines that are reserved for polite users. If you connect
|
120
|
+
# to the Crossreef API using HTTPS and provide contact
|
121
|
+
# information, then they will send you to a separate pool of machines,
|
122
|
+
# with better control the performance of these machines because they can
|
123
|
+
# block abusive users.
|
124
|
+
#
|
125
|
+
# We have been using `https` in `serrano` for a while now, so that's good
|
126
|
+
# to go. To get into the Polite Pool, also set your `mailto` email address
|
127
|
+
# with `Serrano.configuration` (example below), or set as an environment
|
128
|
+
# variable with the name `CROSSREF_EMAIL` and your mailto will be set
|
129
|
+
# for each request automatically.
|
130
|
+
#
|
131
|
+
# require 'serrano'
|
132
|
+
# Serrano.configuration do |config|
|
133
|
+
# config.mailto = "foo@bar.com"
|
134
|
+
# end
|
135
|
+
#
|
95
136
|
#
|
96
137
|
# Rate limiting
|
97
138
|
# Crossref introduced rate limiting recently. The rate limits apparently vary,
|
@@ -99,13 +140,20 @@ require 'rexml/xpath'
|
|
99
140
|
# limit is 50 requests per second. Look for the headers `X-Rate-Limit-Limit`
|
100
141
|
# and `X-Rate-Limit-Interval` in requests to see what the current rate
|
101
142
|
# limits are.
|
143
|
+
#
|
144
|
+
#
|
145
|
+
# URL Encoding
|
146
|
+
# We do URL encoding of DOIs for you for all methods except `Serrano.citation_count`
|
147
|
+
# which doesn't work if you encode DOIs beforehand. We use `ERB::Util.url_encode`
|
148
|
+
# to encode.
|
102
149
|
|
103
150
|
module Serrano
|
104
151
|
extend Configuration
|
105
152
|
|
106
153
|
define_setting :access_token
|
107
154
|
define_setting :access_secret
|
108
|
-
define_setting :
|
155
|
+
define_setting :mailto, ENV['CROSSREF_EMAIL']
|
156
|
+
define_setting :base_url, 'https://api.crossref.org/'
|
109
157
|
|
110
158
|
##
|
111
159
|
# Search the works route
|
@@ -154,8 +202,12 @@ module Serrano
|
|
154
202
|
# # sample
|
155
203
|
# Serrano.works(sample: 2)
|
156
204
|
#
|
205
|
+
# # select - pass an array or a comma separated string
|
206
|
+
# Serrano.works(query: "ecology", select: "DOI,title", limit: 30)
|
207
|
+
# Serrano.works(query: "ecology", select: ["DOI","title"], limit: 30)
|
208
|
+
#
|
157
209
|
# # cursor for deep paging
|
158
|
-
# Serrano.works(query: "widget", cursor: "*", limit: 100)
|
210
|
+
# Serrano.works(query: "widget", cursor: "*", limit: 100, cursor_max: 1000)
|
159
211
|
# # another query, more results this time
|
160
212
|
# res = Serrano.works(query: "science", cursor: "*", limit: 250, cursor_max: 1000);
|
161
213
|
# res.collect { |x| x['message']['items'].length }.reduce(0, :+)
|
@@ -173,13 +225,18 @@ module Serrano
|
|
173
225
|
# ## query.container-title
|
174
226
|
# res = Serrano.works(query: "ecology", query_container_title: 'Ecology')
|
175
227
|
# res['message']['items'].collect { |x| x['container-title'] }
|
228
|
+
#
|
229
|
+
# # select certain fields
|
230
|
+
# Serrano.works(select: ['DOI', 'title'], limit: 3)
|
176
231
|
def self.works(ids: nil, query: nil, filter: nil, offset: nil,
|
177
|
-
|
178
|
-
|
232
|
+
limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
|
233
|
+
select: nil, options: nil, verbose: false, cursor: nil,
|
234
|
+
cursor_max: 5000, **args)
|
179
235
|
|
236
|
+
assert_valid_filters(filter) if filter
|
180
237
|
RequestCursor.new('works', ids, query, filter, offset,
|
181
|
-
|
182
|
-
|
238
|
+
limit, sample, sort, order, facet, select, nil, nil, options,
|
239
|
+
verbose, cursor, cursor_max, args).perform
|
183
240
|
end
|
184
241
|
|
185
242
|
##
|
@@ -225,14 +282,18 @@ module Serrano
|
|
225
282
|
# ## query.title
|
226
283
|
# res = Serrano.members(ids: 221, works: true, query_container_title: 'Advances')
|
227
284
|
# res[0]['message']['items'].collect { |x| x['container-title'] }
|
285
|
+
#
|
286
|
+
# # select certain fields
|
287
|
+
# Serrano.members(ids: 340, works: true, select: ['DOI', 'title'], limit: 3)
|
228
288
|
def self.members(ids: nil, query: nil, filter: nil, offset: nil,
|
229
|
-
|
230
|
-
|
231
|
-
|
289
|
+
limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
|
290
|
+
select: nil, works: false, options: nil, verbose: false,
|
291
|
+
cursor: nil, cursor_max: 5000, **args)
|
232
292
|
|
293
|
+
assert_valid_filters(filter) if filter
|
233
294
|
RequestCursor.new('members', ids, query, filter, offset,
|
234
|
-
|
235
|
-
|
295
|
+
limit, sample, sort, order, facet, select, works, nil,
|
296
|
+
options, verbose, cursor, cursor_max, args).perform
|
236
297
|
end
|
237
298
|
|
238
299
|
##
|
@@ -266,17 +327,21 @@ module Serrano
|
|
266
327
|
# items.collect{ |z| z['DOI'] }[0,50]
|
267
328
|
#
|
268
329
|
# # field queries
|
269
|
-
# ## query.
|
270
|
-
# res = Serrano.prefixes(ids: "10.1016", works: true,
|
330
|
+
# ## query.bibliographic
|
331
|
+
# res = Serrano.prefixes(ids: "10.1016", works: true, query_bibliographic: 'cell biology')
|
271
332
|
# res[0]['message']['items'].collect { |x| x['title'] }
|
333
|
+
#
|
334
|
+
# # select certain fields
|
335
|
+
# Serrano.prefixes(ids: "10.1016", works: true, select: ['DOI', 'title'], limit: 3)
|
272
336
|
def self.prefixes(ids:, filter: nil, offset: nil,
|
273
|
-
|
274
|
-
|
275
|
-
|
337
|
+
limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
|
338
|
+
select: nil, works: false, options: nil, verbose: false,
|
339
|
+
cursor: nil, cursor_max: 5000, **args)
|
276
340
|
|
341
|
+
assert_valid_filters(filter) if filter
|
277
342
|
RequestCursor.new('prefixes', ids, nil, filter, offset,
|
278
|
-
|
279
|
-
|
343
|
+
limit, sample, sort, order, facet, select, works, nil,
|
344
|
+
options, verbose, cursor, cursor_max, args).perform
|
280
345
|
end
|
281
346
|
|
282
347
|
##
|
@@ -318,14 +383,18 @@ module Serrano
|
|
318
383
|
# ## query.title
|
319
384
|
# res = Serrano.funders(ids: "10.13039/100000001", works: true, query_author: 'Simon')
|
320
385
|
# res[0]['message']['items'].collect { |x| x['author'][0]['family'] }
|
386
|
+
#
|
387
|
+
# # select certain fields
|
388
|
+
# Serrano.funders(ids: "10.13039/100000001", works: true, select: ['DOI', 'title'], limit: 3)
|
321
389
|
def self.funders(ids: nil, query: nil, filter: nil, offset: nil,
|
322
|
-
|
323
|
-
|
324
|
-
|
390
|
+
limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
|
391
|
+
select: nil, works: false, options: nil, verbose: false,
|
392
|
+
cursor: nil, cursor_max: 5000, **args)
|
325
393
|
|
394
|
+
assert_valid_filters(filter) if filter
|
326
395
|
RequestCursor.new('funders', ids, query, filter, offset,
|
327
|
-
|
328
|
-
|
396
|
+
limit, sample, sort, order, facet, select, works, nil, options,
|
397
|
+
verbose, cursor, cursor_max, args).perform
|
329
398
|
end
|
330
399
|
|
331
400
|
##
|
@@ -367,14 +436,18 @@ module Serrano
|
|
367
436
|
# ## query.title
|
368
437
|
# res = Serrano.journals(ids: "2167-8359", works: true, query_container_title: 'Advances')
|
369
438
|
# res[0]['message']['items'].collect { |x| x['container-title'] }
|
439
|
+
#
|
440
|
+
# # select certain fields
|
441
|
+
# Serrano.journals(ids: "2167-8359", works: true, select: ['DOI', 'title'], limit: 3)
|
370
442
|
def self.journals(ids: nil, query: nil, filter: nil, offset: nil,
|
371
|
-
|
372
|
-
|
373
|
-
|
443
|
+
limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
|
444
|
+
select: nil, works: false, options: nil, verbose: false,
|
445
|
+
cursor: nil, cursor_max: 5000, **args)
|
374
446
|
|
447
|
+
assert_valid_filters(filter) if filter
|
375
448
|
RequestCursor.new('journals', ids, query, filter, offset,
|
376
|
-
|
377
|
-
|
449
|
+
limit, sample, sort, order, facet, select, works, nil, options,
|
450
|
+
verbose, cursor, cursor_max, args).perform
|
378
451
|
end
|
379
452
|
|
380
453
|
##
|
@@ -385,6 +458,10 @@ module Serrano
|
|
385
458
|
# @!macro field_queries
|
386
459
|
# @param ids [Array] DOIs (digital object identifier) or other identifiers
|
387
460
|
# @param works [Boolean] If true, works returned as well. Default: false
|
461
|
+
# @param select [String/Array(String)] Crossref metadata records can be
|
462
|
+
# quite large. Sometimes you just want a few elements from the schema. You can "select"
|
463
|
+
# a subset of elements to return. This can make your API calls much more efficient. Not
|
464
|
+
# clear yet which fields are allowed here.
|
388
465
|
# @return [Array] An array of hashes
|
389
466
|
#
|
390
467
|
# @example
|
@@ -404,20 +481,42 @@ module Serrano
|
|
404
481
|
# ## query.title
|
405
482
|
# res = Serrano.types(ids: "journal", works: true, query_container_title: 'Advances')
|
406
483
|
# res[0]['message']['items'].collect { |x| x['container-title'] }
|
407
|
-
|
408
|
-
|
484
|
+
#
|
485
|
+
# # select certain fields
|
486
|
+
# Serrano.types(ids: "journal", works: true, select: ['DOI', 'title'], limit: 3)
|
487
|
+
def self.types(ids: nil, offset: nil, limit: nil, select: nil, works: false,
|
488
|
+
options: nil, verbose: false, cursor: nil, cursor_max: 5000, **args)
|
409
489
|
|
410
490
|
RequestCursor.new('types', ids, nil, nil, offset,
|
411
|
-
|
412
|
-
|
491
|
+
limit, nil, nil, nil, nil, select, works, nil, options,
|
492
|
+
verbose, cursor, cursor_max, args).perform
|
413
493
|
end
|
414
494
|
|
415
495
|
##
|
416
496
|
# Search the licenses route
|
417
497
|
#
|
418
|
-
# @!macro serrano_params
|
419
498
|
# @!macro serrano_options
|
420
499
|
# @param query [String] A query string
|
500
|
+
# @param offset [Fixnum] Number of record to start at, any non-negative integer up to 10,000
|
501
|
+
# @param limit [Fixnum] Number of results to return. Not relavant when searching with specific dois.
|
502
|
+
# Default: 20. Max: 1000
|
503
|
+
# @param sample [Fixnum] Number of random results to return. when you use the sample parameter,
|
504
|
+
# the limit and offset parameters are ignored. This parameter only used when works requested.
|
505
|
+
# Max: 100.
|
506
|
+
# @param sort [String] Field to sort on, one of score, relevance,
|
507
|
+
# updated (date of most recent change to metadata - currently the same as deposited),
|
508
|
+
# deposited (time of most recent deposit), indexed (time of most recent index),
|
509
|
+
# published (publication date), published-print (print publication date),
|
510
|
+
# published-online (online publication date), issued (issued date (earliest known publication date)),
|
511
|
+
# is-referenced-by-count (number of times this DOI is referenced by other Crossref DOIs), or
|
512
|
+
# references-count (number of references included in the references section of the document
|
513
|
+
# identified by this DOI). Note: If the API call includes a query, then the sort
|
514
|
+
# order will be by the relevance score. If no query is included, then the sort order
|
515
|
+
# will be by DOI update date.
|
516
|
+
# @param order [String] Sort order, one of 'asc' or 'desc'
|
517
|
+
# @param facet [Boolean/String] Include facet results OR a query (e.g., `license:*`) to facet by
|
518
|
+
# license. Default: false
|
519
|
+
# @param verbose [Boolean] Print request headers to stdout. Default: false
|
421
520
|
# @return [Array] An array of hashes
|
422
521
|
#
|
423
522
|
# @example
|
@@ -426,11 +525,11 @@ module Serrano
|
|
426
525
|
# Serrano.licenses()
|
427
526
|
# Serrano.licenses(limit: 3)
|
428
527
|
def self.licenses(query: nil, offset: nil,
|
429
|
-
|
430
|
-
|
528
|
+
limit: nil, sample: nil, sort: nil, order: nil,
|
529
|
+
facet: nil, options: nil, verbose: false)
|
431
530
|
|
432
531
|
Request.new('licenses', nil, query, nil, offset,
|
433
|
-
|
532
|
+
limit, sample, sort, order, facet, nil, nil, nil, options, verbose).perform
|
434
533
|
end
|
435
534
|
|
436
535
|
##
|
@@ -445,9 +544,8 @@ module Serrano
|
|
445
544
|
# Serrano.registration_agency(ids: '10.1371/journal.pone.0033693')
|
446
545
|
# Serrano.registration_agency(ids: ['10.1007/12080.1874-1746','10.1007/10452.1573-5125', '10.1111/(issn)1442-9993'])
|
447
546
|
def self.registration_agency(ids:, options: nil, verbose: false)
|
448
|
-
|
449
547
|
Request.new('works', ids, nil, nil, nil,
|
450
|
-
|
548
|
+
nil, nil, nil, nil, nil, nil, false, true, options, verbose).perform
|
451
549
|
end
|
452
550
|
|
453
551
|
##
|
@@ -468,9 +566,8 @@ module Serrano
|
|
468
566
|
# Serrano.random_dois(sample: 10)
|
469
567
|
# Serrano.random_dois(sample: 100)
|
470
568
|
def self.random_dois(sample: 10, options: nil, verbose: false)
|
471
|
-
|
472
569
|
tmp = Request.new('works', nil, nil, nil, nil,
|
473
|
-
|
570
|
+
nil, sample, nil, nil, nil, nil, false, nil, options, verbose).perform
|
474
571
|
tmp['message']['items'].collect { |x| x['DOI'] }
|
475
572
|
end
|
476
573
|
|
@@ -497,7 +594,7 @@ module Serrano
|
|
497
594
|
# Serrano.content_negotiation(ids: "10.1126/science.169.3946.635", format: "crossref-xml")
|
498
595
|
# Serrano.content_negotiation(ids: "10.1126/science.169.3946.635", format: "text")
|
499
596
|
#
|
500
|
-
# # return
|
597
|
+
# # return a bibentry type
|
501
598
|
# Serrano.content_negotiation(ids: "10.1126/science.169.3946.635", format: "bibentry")
|
502
599
|
# Serrano.content_negotiation(ids: "10.6084/m9.figshare.97218", format: "bibentry")
|
503
600
|
#
|
@@ -510,30 +607,32 @@ module Serrano
|
|
510
607
|
# Serrano.content_negotiation(ids: "10.1126/science.169.3946.635", format: "text", style: "oikos")
|
511
608
|
#
|
512
609
|
# # example with many DOIs
|
513
|
-
# dois =
|
514
|
-
# Serrano.content_negotiation(dois, format: "text", style: "apa")
|
610
|
+
# dois = Serrano.random_dois(sample: 2)
|
611
|
+
# Serrano.content_negotiation(ids: dois, format: "text", style: "apa")
|
515
612
|
#
|
516
613
|
# # Using DataCite DOIs
|
517
|
-
#
|
518
|
-
#
|
519
|
-
#
|
520
|
-
#
|
614
|
+
# doi = "10.1126/science.169.3946.635"
|
615
|
+
# Serrano.content_negotiation(ids: doi, format: "text")
|
616
|
+
# Serrano.content_negotiation(ids: doi, format: "crossref-xml")
|
617
|
+
# Serrano.content_negotiation(ids: doi, format: "crossref-tdm")
|
521
618
|
#
|
522
619
|
# ## But most do work
|
523
|
-
# Serrano.content_negotiation(ids:
|
524
|
-
# Serrano.content_negotiation(ids:
|
525
|
-
# Serrano.content_negotiation(ids:
|
526
|
-
# Serrano.content_negotiation(ids:
|
527
|
-
# Serrano.content_negotiation(ids:
|
528
|
-
# Serrano.content_negotiation(ids:
|
529
|
-
# Serrano.content_negotiation(ids:
|
530
|
-
# Serrano.content_negotiation(ids:
|
620
|
+
# Serrano.content_negotiation(ids: doi, format: "datacite-xml")
|
621
|
+
# Serrano.content_negotiation(ids: doi, format: "rdf-xml")
|
622
|
+
# Serrano.content_negotiation(ids: doi, format: "turtle")
|
623
|
+
# Serrano.content_negotiation(ids: doi, format: "citeproc-json")
|
624
|
+
# Serrano.content_negotiation(ids: doi, format: "ris")
|
625
|
+
# Serrano.content_negotiation(ids: doi, format: "bibtex")
|
626
|
+
# Serrano.content_negotiation(ids: doi, format: "bibentry")
|
627
|
+
# Serrano.content_negotiation(ids: doi, format: "bibtex")
|
531
628
|
#
|
532
629
|
# # many DOIs
|
533
|
-
# dois = ['10.5167/UZH-30455','10.5167/UZH-49216','10.5167/UZH-503',
|
630
|
+
# dois = ['10.5167/UZH-30455','10.5167/UZH-49216','10.5167/UZH-503',
|
631
|
+
# '10.5167/UZH-38402','10.5167/UZH-41217']
|
534
632
|
# x = Serrano.content_negotiation(ids: dois)
|
535
633
|
# puts x
|
536
|
-
def self.content_negotiation(ids:, format:
|
634
|
+
def self.content_negotiation(ids:, format: 'bibtex', style: 'apa', locale: 'en-US')
|
635
|
+
ids = Array(ids).map { |x| ERB::Util.url_encode(x) }
|
537
636
|
CNRequest.new(ids, format, style, locale).perform
|
538
637
|
end
|
539
638
|
|
@@ -552,17 +651,17 @@ module Serrano
|
|
552
651
|
# Serrano.citation_count(doi: "10.1016/j.fbr.2012.01.001")
|
553
652
|
# # DOI not found
|
554
653
|
# Serrano.citation_count(doi: "10.1016/j.fbr.2012")
|
555
|
-
def self.citation_count(doi:, url:
|
556
|
-
|
654
|
+
def self.citation_count(doi:, url: 'https://www.crossref.org/openurl/',
|
655
|
+
key: 'cboettig@ropensci.org', options: nil)
|
557
656
|
|
558
|
-
args = { id:
|
559
|
-
opts = args.delete_if { |
|
560
|
-
conn = Faraday.new(:
|
657
|
+
args = { id: 'doi:' + doi, pid: key, noredirect: true }
|
658
|
+
opts = args.delete_if { |_k, v| v.nil? }
|
659
|
+
conn = Faraday.new(url: url, request: options)
|
561
660
|
res = conn.get '', opts
|
562
661
|
x = res.body
|
563
662
|
oc = REXML::Document.new("<doc>#{x}</doc>")
|
564
663
|
value = REXML::XPath.first(oc, '//query').attributes['fl_count'].to_i
|
565
|
-
|
664
|
+
value
|
566
665
|
end
|
567
666
|
|
568
667
|
# Get csl styles
|
@@ -572,7 +671,25 @@ module Serrano
|
|
572
671
|
# @example
|
573
672
|
# Serrano.csl_styles
|
574
673
|
def self.csl_styles
|
575
|
-
|
674
|
+
fetch_styles
|
576
675
|
end
|
577
676
|
|
677
|
+
def self.assert_valid_filters(filters)
|
678
|
+
unless filters.is_a? Hash
|
679
|
+
raise ArgumentError, <<~ERR
|
680
|
+
Filters must be provided as a hash, like:
|
681
|
+
|
682
|
+
Serrano.works(query: "something", filters: { has_abstract: true })
|
683
|
+
ERR
|
684
|
+
end
|
685
|
+
|
686
|
+
filters.each do |name, _|
|
687
|
+
filter_strings = Filters.names.map(&:to_s)
|
688
|
+
next if filter_strings.include?(name.to_s)
|
689
|
+
|
690
|
+
raise ArgumentError, <<~ERR
|
691
|
+
The filter "#{name}" is not a valid filter. Please run `Serrano.filters.filters` to see all valid filters.
|
692
|
+
ERR
|
693
|
+
end
|
694
|
+
end
|
578
695
|
end
|