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