aspire 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +59 -0
- data/.rbenv-gemsets +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +851 -0
- data/Rakefile +10 -0
- data/aspire.gemspec +40 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/entrypoint.sh +11 -0
- data/exe/build-cache +13 -0
- data/lib/aspire.rb +11 -0
- data/lib/aspire/api.rb +2 -0
- data/lib/aspire/api/base.rb +198 -0
- data/lib/aspire/api/json.rb +195 -0
- data/lib/aspire/api/linked_data.rb +214 -0
- data/lib/aspire/caching.rb +4 -0
- data/lib/aspire/caching/builder.rb +356 -0
- data/lib/aspire/caching/cache.rb +365 -0
- data/lib/aspire/caching/cache_entry.rb +296 -0
- data/lib/aspire/caching/cache_logger.rb +63 -0
- data/lib/aspire/caching/util.rb +210 -0
- data/lib/aspire/cli/cache_builder.rb +123 -0
- data/lib/aspire/cli/command.rb +20 -0
- data/lib/aspire/enumerator/base.rb +29 -0
- data/lib/aspire/enumerator/json_enumerator.rb +130 -0
- data/lib/aspire/enumerator/linked_data_uri_enumerator.rb +32 -0
- data/lib/aspire/enumerator/report_enumerator.rb +64 -0
- data/lib/aspire/exceptions.rb +36 -0
- data/lib/aspire/object.rb +7 -0
- data/lib/aspire/object/base.rb +155 -0
- data/lib/aspire/object/digitisation.rb +43 -0
- data/lib/aspire/object/factory.rb +87 -0
- data/lib/aspire/object/list.rb +590 -0
- data/lib/aspire/object/module.rb +36 -0
- data/lib/aspire/object/resource.rb +371 -0
- data/lib/aspire/object/time_period.rb +47 -0
- data/lib/aspire/object/user.rb +46 -0
- data/lib/aspire/properties.rb +20 -0
- data/lib/aspire/user_lookup.rb +103 -0
- data/lib/aspire/util.rb +185 -0
- data/lib/aspire/version.rb +3 -0
- data/lib/retry.rb +197 -0
- metadata +274 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'aspire/object/base'
|
2
|
+
require 'aspire/properties'
|
3
|
+
|
4
|
+
module Aspire
|
5
|
+
module Object
|
6
|
+
# Represents a module in the Aspire API
|
7
|
+
class Module < Base
|
8
|
+
include Aspire::Properties
|
9
|
+
|
10
|
+
# @!attribute [rw] code
|
11
|
+
# @return [String] the module code
|
12
|
+
attr_accessor :code
|
13
|
+
|
14
|
+
# @!attribute [rw] name
|
15
|
+
# @return [String] the module name
|
16
|
+
attr_accessor :name
|
17
|
+
|
18
|
+
# Initialises a new Module instance
|
19
|
+
def initialize(uri, factory, json: nil, ld: nil)
|
20
|
+
super(uri, factory)
|
21
|
+
self.code =
|
22
|
+
get_property('code', json) ||
|
23
|
+
get_property(AIISO_CODE, ld)
|
24
|
+
self.name =
|
25
|
+
get_property('name', json) ||
|
26
|
+
get_property(AIISO_NAME, ld)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a string representation of the Module instance (the module name)
|
30
|
+
# @return [String] the string representation of the Module instance
|
31
|
+
def to_s
|
32
|
+
name.to_s || super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,371 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Aspire
|
4
|
+
module Object
|
5
|
+
# Delegates citation_<property> method calls to the parent resource
|
6
|
+
module ResourcePropertyMixin
|
7
|
+
CITATION_PROPERTIES = %w[
|
8
|
+
authors book_jacket_url date doi edition edition_data eissn has_part
|
9
|
+
is_part_of isbn10 isbn13 isbns issn issue issued latest_edition
|
10
|
+
local_control_number online_resource page page_end page_start
|
11
|
+
place_of_publication publisher title type url volume
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# Handles citation_<property>() accessor calls by proxying to the parent
|
15
|
+
# resource if no instance value is set.
|
16
|
+
# The <property> accessor for this instance is called first, and if this
|
17
|
+
# returns nil and there is a parent resource (is_part_of), the property
|
18
|
+
# accessor of the parent is called.
|
19
|
+
# This continues up through the ancestor resources until a value is found.
|
20
|
+
# @param method_name [Symbol] the method name
|
21
|
+
# Positional and keyword arguments are passed to the property accessor
|
22
|
+
def method_missing(method_name, *args, &block)
|
23
|
+
property_name = get_property_name_from_method(method_name)
|
24
|
+
super if property_name.nil?
|
25
|
+
# Try the resource's property first
|
26
|
+
value = public_send(property_name, *args, &block)
|
27
|
+
return value unless value.nil?
|
28
|
+
# Delegate to the parent resource's property if it exists
|
29
|
+
# Call the parent's citation_<property> rather than <property> to
|
30
|
+
# delegate up the ancestor chain.
|
31
|
+
if is_part_of
|
32
|
+
value = is_part_of.public_send(method_name, *args, &block)
|
33
|
+
return value unless value.nil?
|
34
|
+
end
|
35
|
+
# Otherwise return nil
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the property name from a citation_<property> missing method call
|
40
|
+
# @param method_name [Symbol] the method name
|
41
|
+
# @param _include_all [Boolean] if true, include private/protected methods
|
42
|
+
# @return [String, nil] the property name, or nil if not a valid property
|
43
|
+
def get_property_name_from_method(method_name, _include_all = false)
|
44
|
+
# Ignore method names not beginning with citation_
|
45
|
+
return nil unless method_name.to_s.start_with?('citation_')
|
46
|
+
# Remove the 'citation_' prefix to get the property name
|
47
|
+
property = method_name[9..-1]
|
48
|
+
return nil if property.nil? || property.empty?
|
49
|
+
# Accept only whitelisted properties
|
50
|
+
return nil unless CITATION_PROPERTIES.include?(property)
|
51
|
+
# Accept only properties with accessor methods
|
52
|
+
return nil unless respond_to?(property)
|
53
|
+
# Return the property name
|
54
|
+
property
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns true if this method is supported, false if not
|
58
|
+
# @param method_name [Symbol] the method name
|
59
|
+
# @param include_all [Boolean] if true, include private/protected methods
|
60
|
+
# @return [Boolean] true if the method is supported, false otherwise
|
61
|
+
def respond_to_missing?(method_name, include_all = false)
|
62
|
+
property_name = get_property_name_from_method(method_name, include_all)
|
63
|
+
# property_name is not nil if the method is supported
|
64
|
+
!property_name.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Shortcut methods
|
69
|
+
module ResourceShortcutsMixin
|
70
|
+
# Returns the title of the journal article associated with this resource
|
71
|
+
# @return [String, nil] the journal article title or nil if not applicable
|
72
|
+
def article_title
|
73
|
+
part_title_by_type('Article')
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the title of the book associated with this resource
|
77
|
+
# @return [String, nil] the book title or nil if not applicable
|
78
|
+
def book_title
|
79
|
+
part_of_title_by_type('Book')
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the title of the book chapter associated with this resource
|
83
|
+
# @return [String, nil] the book chapter title or nil if not applicable
|
84
|
+
def chapter_title
|
85
|
+
part_title_by_type('Chapter')
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the resource title as expected by the Alma reading list loader
|
89
|
+
# (Article = article title, book = book title, other = resource title)
|
90
|
+
# @return [String] the citation title
|
91
|
+
def citation_title
|
92
|
+
article_title || book_title || title
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the title of the journal associated with this resource
|
96
|
+
# @return [String, nil] the journal title or nil if not applicable
|
97
|
+
def journal_title
|
98
|
+
part_of_title_by_type('Journal')
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the title of the parent resource (book, journal etc.)
|
102
|
+
# @return [String] the title of the parent resource
|
103
|
+
def part_of_title
|
104
|
+
is_part_of ? is_part_of.title : nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the title of the parent resource (book, journal etc.)
|
108
|
+
# @return [String] the title of the parent resource
|
109
|
+
def part_of_title_by_type(res_type)
|
110
|
+
return title if type == res_type
|
111
|
+
return is_part_of.title if is_part_of && is_part_of.type == res_type
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the title of the part (book chapter, journal article etc.)
|
116
|
+
# @return [String] the title of the part
|
117
|
+
def part_title
|
118
|
+
has_part ? has_part.title : nil
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns the title of the part
|
122
|
+
# @param res_type [String] the type of the resource
|
123
|
+
# @return [String] the title of the part
|
124
|
+
def part_title_by_type(res_type)
|
125
|
+
return title if type == res_type
|
126
|
+
return has_part.title if has_part && has_part.type == res_type
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Represents a resource in the Aspire API
|
132
|
+
class Resource < Base
|
133
|
+
include ResourcePropertyMixin
|
134
|
+
include ResourceShortcutsMixin
|
135
|
+
|
136
|
+
PAGE_RANGE = /(?<start>[\da-zA-Z]*)\s*-\s*(?<end>[\da-zA-Z]*)/
|
137
|
+
|
138
|
+
# @!attribute [rw] authors
|
139
|
+
# @return [Array<String>] the list of authors of the resource
|
140
|
+
attr_accessor :authors
|
141
|
+
|
142
|
+
# @!attribute [rw] book_jacket_url
|
143
|
+
# @return [String] the book jacket image URL
|
144
|
+
attr_accessor :book_jacket_url
|
145
|
+
|
146
|
+
# @!attribute [rw] date
|
147
|
+
# @return [String] the date of publication
|
148
|
+
attr_accessor :date
|
149
|
+
|
150
|
+
# @!attribute [rw] doi
|
151
|
+
# @return [String] the DOI for the resource
|
152
|
+
attr_accessor :doi
|
153
|
+
|
154
|
+
# @!attribute [rw] edition
|
155
|
+
# @return [String] the edition
|
156
|
+
attr_accessor :edition
|
157
|
+
|
158
|
+
# @!attribute [rw] edition_data
|
159
|
+
# @return [Boolean] true if edition data is available
|
160
|
+
attr_accessor :edition_data
|
161
|
+
|
162
|
+
# @!attribute [rw] eissn
|
163
|
+
# @return [String] the electronic ISSN for the resource
|
164
|
+
attr_accessor :eissn
|
165
|
+
|
166
|
+
# @!attribute [rw] has_part
|
167
|
+
# @return [Array<Aspire::Object::Resource>] child resources
|
168
|
+
attr_accessor :has_part
|
169
|
+
|
170
|
+
# @!attribute [rw] is_part_of
|
171
|
+
# @return [Array<Aspire::Object::Resource>] parent resources
|
172
|
+
attr_accessor :is_part_of
|
173
|
+
|
174
|
+
# @!attribute [rw] isbn10
|
175
|
+
# @return [String] the 10-digit ISBN for the resource
|
176
|
+
attr_accessor :isbn10
|
177
|
+
|
178
|
+
# @!attribute [rw] isbn13
|
179
|
+
# @return [String] the 13-digit ISBN for the resource
|
180
|
+
attr_accessor :isbn13
|
181
|
+
|
182
|
+
# @!attribute [rw] isbns
|
183
|
+
# @return [Array<String>] the list of ISBNs for the resource
|
184
|
+
attr_accessor :isbns
|
185
|
+
|
186
|
+
# @!attribute [rw] issn
|
187
|
+
# @return [Array<String>] the ISSN for the resource
|
188
|
+
attr_accessor :issn
|
189
|
+
|
190
|
+
# @!attribute [rw] issue
|
191
|
+
# @return [String] the issue
|
192
|
+
attr_accessor :issue
|
193
|
+
|
194
|
+
# @!attribute [rw] issued
|
195
|
+
# @return [String] the issue date
|
196
|
+
attr_accessor :issued
|
197
|
+
|
198
|
+
# @!attribute [rw] latest_edition
|
199
|
+
# @return [Boolean] true if this is the latest edition
|
200
|
+
attr_accessor :latest_edition
|
201
|
+
|
202
|
+
# @!attribute [rw] local_control_number
|
203
|
+
# @return [String] the local control number in the library catalogue
|
204
|
+
attr_accessor :local_control_number
|
205
|
+
|
206
|
+
# @!attribute [rw] online_resource
|
207
|
+
# @return [Boolean] true if this is an online resource
|
208
|
+
attr_accessor :online_resource
|
209
|
+
|
210
|
+
# @!attribute [rw] page
|
211
|
+
# @return [String] the page range
|
212
|
+
attr_accessor :page
|
213
|
+
|
214
|
+
# @!attribute [rw] page_end
|
215
|
+
# @return [String] the end page
|
216
|
+
attr_accessor :page_end
|
217
|
+
|
218
|
+
# @!attribute [rw] page_start
|
219
|
+
# @return [String] the start page
|
220
|
+
attr_accessor :page_start
|
221
|
+
|
222
|
+
# @!attribute [rw] place_of_publication
|
223
|
+
# @return [String] the place of publication
|
224
|
+
attr_accessor :place_of_publication
|
225
|
+
|
226
|
+
# @!attribute [rw] publisher
|
227
|
+
# @return [String] the publisher
|
228
|
+
attr_accessor :publisher
|
229
|
+
|
230
|
+
# @!attribute [rw] title
|
231
|
+
# @return [String] the title of the resource
|
232
|
+
attr_accessor :title
|
233
|
+
|
234
|
+
# @!attribute [rw] type
|
235
|
+
# @return [String] the type of the resource
|
236
|
+
attr_accessor :type
|
237
|
+
|
238
|
+
# @!attribute [rw] url
|
239
|
+
# @return [String] the URL of the resource
|
240
|
+
attr_accessor :url
|
241
|
+
|
242
|
+
# @!attribute [rw] volume
|
243
|
+
# @return [String] the volume
|
244
|
+
attr_accessor :volume
|
245
|
+
|
246
|
+
# Initialises a new Resource instance
|
247
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
248
|
+
# @param ld [Hash] the resource data from the Aspire linked data API
|
249
|
+
# @return [void]
|
250
|
+
def initialize(uri = nil, factory = nil, json: nil, ld: nil)
|
251
|
+
uri ||= json ? json['uri'] : nil
|
252
|
+
super(uri, factory)
|
253
|
+
return unless json
|
254
|
+
init_general(json)
|
255
|
+
init_components(json)
|
256
|
+
init_edition(json)
|
257
|
+
init_identifiers(json)
|
258
|
+
init_part(json)
|
259
|
+
init_publication(json)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Sets the page range and updates the page_start and page_end properties
|
263
|
+
# @param value [String] the page range "start-end"
|
264
|
+
# @return [String] the page range "start-end"
|
265
|
+
def page=(value)
|
266
|
+
@page = value.to_s
|
267
|
+
match = PAGE_RANGE.match(@page)
|
268
|
+
if match.nil?
|
269
|
+
# Value is not a range, treat as a single page
|
270
|
+
@page_end = @page
|
271
|
+
@page_start = @page
|
272
|
+
else
|
273
|
+
# Value is a range
|
274
|
+
@page_end = match[:end]
|
275
|
+
@page_start = match[:start]
|
276
|
+
end
|
277
|
+
@page
|
278
|
+
end
|
279
|
+
|
280
|
+
# Returns the page range spanned by the page_start and page_end properties
|
281
|
+
# @return [String] the page range "start-end" or page number
|
282
|
+
def page_range
|
283
|
+
return @page_start if @page_end.nil? || @page_start == @page_end
|
284
|
+
return @page_end if @page_start.nil?
|
285
|
+
"#{@page_start}-#{@page_end}"
|
286
|
+
end
|
287
|
+
|
288
|
+
# Returns a string representation of the resource (the title)
|
289
|
+
# @return [String] the string representation of the resource
|
290
|
+
def to_s
|
291
|
+
title
|
292
|
+
end
|
293
|
+
|
294
|
+
protected
|
295
|
+
|
296
|
+
# Sets the component-related properties
|
297
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
298
|
+
# @return [void]
|
299
|
+
def init_components(json)
|
300
|
+
has_part = json['hasPart']
|
301
|
+
is_part_of = json['isPartOf']
|
302
|
+
self.has_part = has_part ? factory.get(uri, json: has_part) : nil
|
303
|
+
self.is_part_of = is_part_of ? factory.get(uri, json: is_part_of) : nil
|
304
|
+
end
|
305
|
+
|
306
|
+
# Sets the edition-related properties
|
307
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
308
|
+
# @return [void]
|
309
|
+
def init_edition(json)
|
310
|
+
self.edition = get_property('edition', json)
|
311
|
+
self.edition_data = get_property('editionData', json)
|
312
|
+
self.latest_edition = get_property('latestEdition', json)
|
313
|
+
end
|
314
|
+
|
315
|
+
# Sets general resource properties
|
316
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
317
|
+
# @return [void]
|
318
|
+
def init_general(json)
|
319
|
+
self.authors = get_property('authors', json, single: false)
|
320
|
+
self.book_jacket_url = get_property('bookjacketURL', json)
|
321
|
+
self.issued = json ? json['issued'] : nil # TODO
|
322
|
+
self.online_resource = get_boolean('onlineResource', json)
|
323
|
+
self.title = get_property('title', json)
|
324
|
+
self.type = get_property('type', json)
|
325
|
+
self.url = get_property('url', json, is_url: true)
|
326
|
+
end
|
327
|
+
|
328
|
+
# Sets the identifier properties (DOI, ISBN/ISSN, local control number)
|
329
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
330
|
+
# @return [void]
|
331
|
+
def init_identifiers(json)
|
332
|
+
self.doi = get_property('doi', json)
|
333
|
+
self.eissn = get_property('eissn', json)
|
334
|
+
self.isbn10 = get_property('isbn10', json)
|
335
|
+
self.isbn13 = get_property('isbn13', json)
|
336
|
+
self.isbns = get_property('isbns', json, single: false)
|
337
|
+
self.issn = get_property('issn', json)
|
338
|
+
self.local_control_number = get_property('lcn', json)
|
339
|
+
end
|
340
|
+
|
341
|
+
# Sets the pagination-related properties
|
342
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
343
|
+
# @return [void]
|
344
|
+
def init_pagination(json)
|
345
|
+
self.page_end = get_property('pageEnd', json)
|
346
|
+
self.page_start = get_property('pageStart', json)
|
347
|
+
# Override page_end and page_start if the page range is specified
|
348
|
+
range = get_property('page', json)
|
349
|
+
self.page = range unless range.nil? || range.empty?
|
350
|
+
end
|
351
|
+
|
352
|
+
# Sets the part-related properties
|
353
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
354
|
+
# @return [void]
|
355
|
+
def init_part(json)
|
356
|
+
init_pagination(json)
|
357
|
+
self.issue = get_property('issue', json)
|
358
|
+
self.volume = get_property('volume', json)
|
359
|
+
end
|
360
|
+
|
361
|
+
# Sets the publication-related properties
|
362
|
+
# @param json [Hash] the resource data from the Aspire JSON API
|
363
|
+
# @return [void]
|
364
|
+
def init_publication(json)
|
365
|
+
self.date = get_property('date', json)
|
366
|
+
self.place_of_publication = get_property('placeOfPublication', json)
|
367
|
+
self.publisher = get_property('publisher', json)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'aspire/object/base'
|
2
|
+
|
3
|
+
module Aspire
|
4
|
+
module Object
|
5
|
+
# Represents the time period covered by a reading list in the Aspire API
|
6
|
+
class TimePeriod < Base
|
7
|
+
# @!attribute [rw] active
|
8
|
+
# @return [Boolean] true if the time period is currently active
|
9
|
+
attr_accessor :active
|
10
|
+
|
11
|
+
# @!attribute [rw] end_date
|
12
|
+
# @return [Date] the end of the time period
|
13
|
+
attr_accessor :end_date
|
14
|
+
|
15
|
+
# @!attribute [rw] start_date
|
16
|
+
# @return [Date] the start of the time period
|
17
|
+
attr_accessor :start_date
|
18
|
+
|
19
|
+
# @!attribute [rw] title
|
20
|
+
# @return [String] the title of the time period
|
21
|
+
attr_accessor :title
|
22
|
+
|
23
|
+
# Initialises a new TimePeriod instance
|
24
|
+
def initialize(uri, factory, json: nil, ld: nil)
|
25
|
+
super(uri, factory)
|
26
|
+
json ||= {}
|
27
|
+
self.active = get_property('active', json)
|
28
|
+
self.end_date = get_date('endDate', json)
|
29
|
+
self.start_date = get_date('startDate', json)
|
30
|
+
self.title = get_property('title', json)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a string representation of the TimePeriod instance (the title)
|
34
|
+
# @return [String] the string representation of the TimePeriod instance
|
35
|
+
def to_s
|
36
|
+
title.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the academic year containing this time period
|
40
|
+
# @return [Integer, nil] the academic year, or nil if unspecified
|
41
|
+
def year
|
42
|
+
result = title.split('-')[0]
|
43
|
+
result ? result.to_i : nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|