serrano 0.3.6 → 0.6.2

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.
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