serrano 0.3.6 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,41 +1,49 @@
1
- require "bundler/gem_tasks"
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 << "test"
6
- t.test_files = FileList['test/test-*.rb']
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 "Run tests"
11
- task :default => :test
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 "Build serrano docs"
21
+ desc 'Build serrano docs'
14
22
  task :docs do
15
- system "yardoc"
23
+ system 'yardoc'
16
24
  end
17
25
 
18
- desc "bundle install"
26
+ desc 'bundle install'
19
27
  task :bundle do
20
- system "bundle install"
28
+ system 'bundle install'
21
29
  end
22
30
 
23
- desc "clean out builds"
31
+ desc 'clean out builds'
24
32
  task :clean do
25
- system "ls | grep [0-9].gem | xargs rm"
33
+ system 'ls | grep [0-9].gem | xargs rm'
26
34
  end
27
35
 
28
- desc "Build serrano"
36
+ desc 'Build serrano'
29
37
  task :build do
30
- system "gem build serrano.gemspec"
38
+ system 'gem build serrano.gemspec'
31
39
  end
32
40
 
33
- desc "Install serrano"
34
- task :install => [:bundle, :build] do
35
- system "gem install serrano-#{Serrano::VERSION}.gem"
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 "Release to Rubygems"
39
- task :release => :build do
46
+ desc 'Release to Rubygems'
47
+ task release: :build do
40
48
  system "gem push serrano-#{Serrano::VERSION}.gem"
41
49
  end
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "thor"
4
- require "serrano"
5
- require "multi_json"
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 "works [DOIs]", "Get works by DOIs"
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, :type => :boolean, :default => false
51
- option :filter, :type => :hash, :default => nil
52
- option :limit, :type => :numeric, :default => nil
53
- def works(ids=nil)
54
- if ids.nil?
55
- out = Serrano.works(filter: options[:filter], limit: options[:limit])
56
- else
57
- out = Serrano.works(ids: ids.split(","), filter: options[:filter])
58
- end
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,v| k[/DOI|type|title/] } }
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 "members [member IDs]", "Get members by id"
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, :type => :boolean, :default => false
100
- option :query, :type => :string, :default => nil
101
- option :limit, :type => :numeric, :default => nil
102
- option :works, :type => :boolean, :default => false
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
- works: options[:works])
107
+ works: options[:works])
107
108
  else
108
- out = Serrano.members(ids: ids.split(","), query: options[:query], limit: options[:limit],
109
- works: options[:works])
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
- out = out['message']['items']
116
- else
117
- out = out.collect { |x| x['message'] }
118
- end
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, :type => :boolean, :default => false
166
- option :limit, :type => :numeric, :default => nil
167
- option :works, :type => :boolean, :default => false
168
- def prefixes(ids=nil)
169
- if ids.nil?
170
- out = Serrano.prefixes(limit: options[:limit], works: options[:works])
171
- else
172
- out = Serrano.prefixes(ids: ids.split(","), limit: options[:limit],
173
- works: options[:works])
174
- end
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
- out = out['message']['items']
180
- else
181
- out = out.collect { |x| x['message'] }
182
- end
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 "funders [funder IDs]", "Search for funders by DOI prefix"
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, :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)
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
- works: options[:works])
236
+ works: options[:works])
237
237
  else
238
- out = Serrano.funders(ids: ids.split(","), query: options[:query], limit: options[:limit],
239
- works: options[:works])
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
- out = out['message']['items']
246
- else
247
- out = out.collect { |x| x['message'] }
248
- end
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, :type => :boolean, :default => false
297
- option :query, :type => :string, :default => nil
298
- option :limit, :type => :numeric, :default => nil
299
- option :works, :type => :boolean, :default => false
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
- works: options[:works])
302
+ works: options[:works])
304
303
  else
305
- out = Serrano.journals(ids: ids.split(","), query: options[:query], limit: options[:limit],
306
- works: options[:works])
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
- out = out['message']['items']
313
- else
314
- out = out.collect { |x| x['message'] }
315
- end
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 "types [type name]", "Search for types by name"
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, :type => :boolean, :default => false
363
- option :works, :type => :boolean, :default => false
364
- option :limit, :type => :numeric, :default => nil
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
- works: options[:works])
366
+ works: options[:works])
368
367
 
369
368
  if !options[:json]
370
369
  if !options[:works]
371
- if out.class == Hash
372
- out = out['message']['items']
373
- else
374
- out = out.collect { |x| x['message'] }
375
- end
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 "licenses", "Search for licenses by name"
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, :type => :boolean, :default => false
420
- option :query, :type => :string, :default => nil
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 "contneg", "Content negotiation"
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, :type => :string, :default => "text"
448
- option :style, :type => :string, :default => "apa"
449
- option :locale, :type => :string, :default => "en-US"
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 "version", "Get serrano version"
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
- data = data[0]['message']['items']
465
- else
466
- data = data.collect { |x| x['message'].select { |k,v| k[/DOI|type|title/] } }
467
- end
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)
@@ -1,10 +1,13 @@
1
- require "serrano/version"
2
- require "serrano/request"
3
- require "serrano/request_cursor"
4
- require "serrano/filterhandler"
5
- require "serrano/cnrequest"
6
- require "serrano/filters"
7
- require "serrano/styles"
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), or
22
- # published (publication date). Note: If the API call includes a query, then the sort
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
- # See https://github.com/CrossRef/rest-api-doc/blob/master/rest_api.md#field-queries
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://github.com/CrossRef/rest-api-doc/issues/101
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 :base_url, "https://api.crossref.org/"
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
- limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
178
- options: nil, verbose: false, cursor: nil, cursor_max: 5000, **args)
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
- limit, sample, sort, order, facet, nil, nil, options,
182
- verbose, cursor, cursor_max, args).perform
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
- limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
230
- works: false, options: nil, verbose: false,
231
- cursor: nil, cursor_max: 5000, **args)
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
- limit, sample, sort, order, facet, works, nil, options,
235
- verbose, cursor, cursor_max, args).perform
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.title
270
- # res = Serrano.prefixes(ids: "10.1016", works: true, query_title: 'cell biology')
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
- limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
274
- works: false, options: nil, verbose: false,
275
- cursor: nil, cursor_max: 5000, **args)
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
- limit, sample, sort, order, facet, works, nil, options,
279
- verbose, cursor, cursor_max, args).perform
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
- limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
323
- works: false, options: nil, verbose: false,
324
- cursor: nil, cursor_max: 5000, **args)
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
- limit, sample, sort, order, facet, works, nil, options,
328
- verbose, cursor, cursor_max, args).perform
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
- limit: nil, sample: nil, sort: nil, order: nil, facet: nil,
372
- works: false, options: nil, verbose: false,
373
- cursor: nil, cursor_max: 5000, **args)
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
- limit, sample, sort, order, facet, works, nil, options,
377
- verbose, cursor, cursor_max, args).perform
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
- def self.types(ids: nil, offset: nil, limit: nil, works: false,
408
- options: nil, verbose: false, cursor: nil, cursor_max: 5000, **args)
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
- limit, nil, nil, nil, nil, works, nil, options,
412
- verbose, cursor, cursor_max, args).perform
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
- limit: nil, sample: nil, sort: nil, order: nil,
430
- facet: nil, options: nil, verbose: false)
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
- limit, sample, sort, order, facet, nil, nil, options, verbose).perform
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
- nil, nil, nil, nil, nil, false, true, options, verbose).perform
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
- nil, sample, nil, nil, nil, false, nil, options, verbose).perform
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 an R bibentry type
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 = cr_r(2)
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
- # ## some formats don't work
518
- # # Serrano.content_negotiation(ids: "10.5284/1011335", format: "text")
519
- # # Serrano.content_negotiation(ids: "10.5284/1011335", format: "crossref-xml")
520
- # # Serrano.content_negotiation(ids: "10.5284/1011335", format: "crossref-tdm")
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: "10.5284/1011335", format: "datacite-xml")
524
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "rdf-xml")
525
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "turtle")
526
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "citeproc-json")
527
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "ris")
528
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "bibtex")
529
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "bibentry")
530
- # Serrano.content_negotiation(ids: "10.5284/1011335", format: "bibtex")
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', '10.5167/UZH-38402','10.5167/UZH-41217']
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: "bibtex", style: 'apa', locale: "en-US")
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: "http://www.crossref.org/openurl/",
556
- key: "cboettig@ropensci.org", options: nil)
654
+ def self.citation_count(doi:, url: 'https://www.crossref.org/openurl/',
655
+ key: 'cboettig@ropensci.org', options: nil)
557
656
 
558
- args = { id: "doi:" + doi, pid: key, noredirect: true }
559
- opts = args.delete_if { |k, v| v.nil? }
560
- conn = Faraday.new(:url => url, :request => options)
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
- return value
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
- get_styles()
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