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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +59 -0
  3. data/.rbenv-gemsets +1 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Dockerfile +20 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +851 -0
  10. data/Rakefile +10 -0
  11. data/aspire.gemspec +40 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/entrypoint.sh +11 -0
  15. data/exe/build-cache +13 -0
  16. data/lib/aspire.rb +11 -0
  17. data/lib/aspire/api.rb +2 -0
  18. data/lib/aspire/api/base.rb +198 -0
  19. data/lib/aspire/api/json.rb +195 -0
  20. data/lib/aspire/api/linked_data.rb +214 -0
  21. data/lib/aspire/caching.rb +4 -0
  22. data/lib/aspire/caching/builder.rb +356 -0
  23. data/lib/aspire/caching/cache.rb +365 -0
  24. data/lib/aspire/caching/cache_entry.rb +296 -0
  25. data/lib/aspire/caching/cache_logger.rb +63 -0
  26. data/lib/aspire/caching/util.rb +210 -0
  27. data/lib/aspire/cli/cache_builder.rb +123 -0
  28. data/lib/aspire/cli/command.rb +20 -0
  29. data/lib/aspire/enumerator/base.rb +29 -0
  30. data/lib/aspire/enumerator/json_enumerator.rb +130 -0
  31. data/lib/aspire/enumerator/linked_data_uri_enumerator.rb +32 -0
  32. data/lib/aspire/enumerator/report_enumerator.rb +64 -0
  33. data/lib/aspire/exceptions.rb +36 -0
  34. data/lib/aspire/object.rb +7 -0
  35. data/lib/aspire/object/base.rb +155 -0
  36. data/lib/aspire/object/digitisation.rb +43 -0
  37. data/lib/aspire/object/factory.rb +87 -0
  38. data/lib/aspire/object/list.rb +590 -0
  39. data/lib/aspire/object/module.rb +36 -0
  40. data/lib/aspire/object/resource.rb +371 -0
  41. data/lib/aspire/object/time_period.rb +47 -0
  42. data/lib/aspire/object/user.rb +46 -0
  43. data/lib/aspire/properties.rb +20 -0
  44. data/lib/aspire/user_lookup.rb +103 -0
  45. data/lib/aspire/util.rb +185 -0
  46. data/lib/aspire/version.rb +3 -0
  47. data/lib/retry.rb +197 -0
  48. 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