ro-crate 0.4.8 → 0.4.12

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +13 -13
  3. data/README.md +39 -0
  4. data/lib/ro_crate/model/contextual_entity.rb +2 -14
  5. data/lib/ro_crate/model/crate.rb +52 -6
  6. data/lib/ro_crate/model/data_entity.rb +6 -5
  7. data/lib/ro_crate/model/directory.rb +3 -5
  8. data/lib/ro_crate/model/entity.rb +65 -6
  9. data/lib/ro_crate/model/entry.rb +2 -2
  10. data/lib/ro_crate/model/file.rb +2 -4
  11. data/lib/ro_crate/model/metadata.rb +9 -1
  12. data/lib/ro_crate/model/organization.rb +1 -1
  13. data/lib/ro_crate/model/preview.rb +3 -15
  14. data/lib/ro_crate/model/preview_generator.rb +40 -0
  15. data/lib/ro_crate/model/remote_entry.rb +1 -12
  16. data/lib/ro_crate/reader.rb +77 -20
  17. data/lib/ro_crate/writer.rb +4 -4
  18. data/lib/ro_crate.rb +2 -1
  19. data/ro_crate.gemspec +3 -3
  20. data/test/crate_test.rb +37 -3
  21. data/test/directory_test.rb +21 -21
  22. data/test/entity_test.rb +135 -0
  23. data/test/fixtures/biobb_hpc_workflows-condapack.zip +0 -0
  24. data/test/fixtures/conflicting_data_directory/info.txt +1 -0
  25. data/test/fixtures/conflicting_data_directory/nested.txt +1 -0
  26. data/test/fixtures/nested_directory.zip +0 -0
  27. data/test/fixtures/ro-crate-galaxy-sortchangecase/LICENSE +176 -0
  28. data/test/fixtures/ro-crate-galaxy-sortchangecase/README.md +6 -0
  29. data/test/fixtures/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json +133 -0
  30. data/test/fixtures/ro-crate-galaxy-sortchangecase/sort-and-change-case.ga +118 -0
  31. data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/input.bed +3 -0
  32. data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/output_exp.bed +3 -0
  33. data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/sort-and-change-case-test.yml +8 -0
  34. data/test/fixtures/sparse_directory_crate/ro-crate-preview.html +60 -59
  35. data/test/reader_test.rb +83 -40
  36. data/test/test_helper.rb +5 -1
  37. data/test/writer_test.rb +59 -2
  38. metadata +26 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7003f224766d4445498db499257b246ea2dbfb842f0f5e0c8d04471ccea4d299
4
- data.tar.gz: f62e7c90a54f26aa6b18cf98310c998ada8c6836975c997f551b2b1ab7d85e29
3
+ metadata.gz: d9f081110d1e5b5e94674cb53d07262fbce50ec3f63437d5c4c1ecda8b04f835
4
+ data.tar.gz: 0cd710a9cd86063e706fd9cf338e5fdf55f5bc6993f306af4f50d6f9191a0d87
5
5
  SHA512:
6
- metadata.gz: f63fa2c3b9d88ec76f20e8986986d81878dc8bbb82bedd4c2c862ca6fca7f021ad84309678abb12ec6de7fb498a87711409f3f64809aba4c09c5b00676c60aac
7
- data.tar.gz: 16616f82e91760e9a6ff3eff1199c327143401086b0f9c577908e28d023e27828b7e36eeffc65333ec3aa440697b8f18cddd9406185f116ccc59ff238c98b1e9
6
+ metadata.gz: 8ce573fbd259108edbb528e24f0d9647cfee4454378d57a4b3392aaeb0dea80385b2828221a0979caa4733be0e2108a4a535725846f576c9ac97866f46911e47
7
+ data.tar.gz: ffc7aafbdccb3f4897ad2acae726714d7a313f02fd87e5c96173da2b12860dfa1318b9b323dde751e3ac10c9a66cf29effea383037bc4622eaf5113dd01bd426
data/Gemfile.lock CHANGED
@@ -1,31 +1,31 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ro-crate (0.4.8)
5
- addressable (~> 2.7.0)
4
+ ro-crate (0.4.12)
5
+ addressable (>= 2.7, < 2.9)
6
6
  rubyzip (~> 2.0.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- addressable (2.7.0)
11
+ addressable (2.8.0)
12
12
  public_suffix (>= 2.0.2, < 5.0)
13
13
  crack (0.4.3)
14
14
  safe_yaml (~> 1.0.0)
15
- docile (1.3.1)
15
+ docile (1.3.5)
16
16
  hashdiff (1.0.1)
17
- json (2.3.1)
18
- power_assert (0.4.1)
19
- public_suffix (4.0.3)
17
+ power_assert (1.1.3)
18
+ public_suffix (4.0.6)
20
19
  rake (13.0.0)
21
20
  rubyzip (2.0.0)
22
21
  safe_yaml (1.0.5)
23
- simplecov (0.16.1)
22
+ simplecov (0.21.2)
24
23
  docile (~> 1.1)
25
- json (>= 1.8, < 3)
26
- simplecov-html (~> 0.10.0)
27
- simplecov-html (0.10.2)
28
- test-unit (3.2.3)
24
+ simplecov-html (~> 0.11)
25
+ simplecov_json_formatter (~> 0.1)
26
+ simplecov-html (0.12.3)
27
+ simplecov_json_formatter (0.1.2)
28
+ test-unit (3.2.9)
29
29
  power_assert
30
30
  webmock (3.8.3)
31
31
  addressable (>= 2.3.6)
@@ -39,7 +39,7 @@ PLATFORMS
39
39
  DEPENDENCIES
40
40
  rake (~> 13.0.0)
41
41
  ro-crate!
42
- simplecov (~> 0.16.1)
42
+ simplecov (~> 0.21.2)
43
43
  test-unit (~> 3.2.3)
44
44
  webmock (~> 3.8.3)
45
45
  yard (~> 0.9.25)
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # ro-crate-ruby
2
2
 
3
+ ![Tests](https://github.com/ResearchObject/ro-crate-ruby/actions/workflows/tests.yml/badge.svg)
4
+
3
5
  This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.1 of the specification).
4
6
 
5
7
  * RO-Crate - https://researchobject.github.io/ro-crate/
@@ -17,6 +19,43 @@ and run `bundle install`.
17
19
 
18
20
  ## Usage
19
21
 
22
+ This gem consists a hierarchy of classes to model RO-Crate "entities": the crate itself, data entities
23
+ (files and directory) and contextual entities (with a limited set of specializations, such as `ROCrate::Person`).
24
+ They are all descendents of the `ROCrate::Entity` class, with the `ROCrate::Crate` class representing the crate itself.
25
+
26
+ The `ROCrate::Reader` class handles reading of RO-Crates into the above model, from a Zip file or directory.
27
+
28
+ The `ROCrate::Writer` class can write out an `ROCrate::Crate` instance into a Zip file or directory.
29
+
30
+ **Note:** for performance reasons, the gem is currently not linked-data aware and will allow you to set properties that
31
+ are not semantically valid.
32
+
33
+ ### Entities
34
+ Entities correspond to entries in the `@graph` of the RO-Crate's metadata JSON-LD file. Each entity class is
35
+ basically a wrapper around a set of JSON properties, with some convenience methods for getting/setting some
36
+ commonly used properties (`crate.name = "My first crate"`).
37
+
38
+ These convenience getter/setter methods will automatically handle turning objects into references and adding them to the
39
+ `@graph` if necessary.
40
+
41
+ ##### Getting/Setting Arbitrary Properties of Entities
42
+ As well as using the pre-defined getter/setter methods, you can get/set arbitrary properties like so.
43
+
44
+ To set the "creativeWorkStatus" property of the RO-Crate itself to a string literal:
45
+ ```ruby
46
+ crate['creativeWorkStatus'] = 'work-in-progress'
47
+ ```
48
+
49
+ If you want to reference other entities in the crate, you can get a JSON-LD reference from an entity object by using the `reference` method:
50
+ ```ruby
51
+ joe = crate.add_person('joe', { name: 'Joe Bloggs' }) # Add the entity to the @graph
52
+ crate['copyrightHolder'] = joe.reference # Reference the entity from the "copyrightHolder" property
53
+ ```
54
+ and to resolve those references back to the object, use the `dereference` method:
55
+ ```ruby
56
+ joe = crate['copyrightHolder'].dereference
57
+ ```
58
+
20
59
  ### Documentation
21
60
 
22
61
  [Click here for API documentation](https://www.researchobject.org/ro-crate-ruby/).
@@ -3,21 +3,9 @@ module ROCrate
3
3
  # A class to represent a "Contextual Entity" within an RO-Crate.
4
4
  # Contextual Entities are used to describe and provide context to the Data Entities within the crate.
5
5
  class ContextualEntity < Entity
6
- def self.format_id(id)
6
+ def self.format_local_id(id)
7
7
  i = super
8
- begin
9
- uri = URI(id)
10
- rescue ArgumentError
11
- uri = nil
12
- end
13
-
14
- if uri&.absolute?
15
- i
16
- elsif i.start_with?('#')
17
- i
18
- else
19
- "##{i}"
20
- end
8
+ i.start_with?('#') ? i : "##{i}"
21
9
  end
22
10
 
23
11
  ##
@@ -8,6 +8,11 @@ module ROCrate
8
8
  properties(%w[name datePublished author license identifier distribution contactPoint publisher description url hasPart])
9
9
 
10
10
  def self.format_id(id)
11
+ i = super(id)
12
+ i.end_with?('/') ? i : "#{i}/"
13
+ end
14
+
15
+ def self.format_local_id(id)
11
16
  return id if id == IDENTIFIER
12
17
  super
13
18
  end
@@ -20,6 +25,15 @@ module ROCrate
20
25
  super(self, nil, id, properties)
21
26
  end
22
27
 
28
+ ##
29
+ # Lookup an Entity using the given ID (in this Entity's crate).
30
+ #
31
+ # @param id [String] The ID to query.
32
+ # @return [Entity, nil]
33
+ def dereference(id)
34
+ entities.detect { |e| e.canonical_id == crate.resolve_id(id) } if id
35
+ end
36
+
23
37
  ##
24
38
  # Create a new file and add it to the crate.
25
39
  #
@@ -163,6 +177,15 @@ module ROCrate
163
177
  @preview ||= ROCrate::Preview.new(self)
164
178
  end
165
179
 
180
+ ##
181
+ # Set the RO-Crate preview file
182
+ # @param preview [Preview] the preview to set.
183
+ #
184
+ # @return [Preview]
185
+ def preview=(preview)
186
+ @preview = claim(preview)
187
+ end
188
+
166
189
  ##
167
190
  # All the entities within the crate. Includes contextual entities, data entities, the crate itself and its metadata file.
168
191
  #
@@ -215,21 +238,21 @@ module ROCrate
215
238
  entity.class.new(crate, entity.id, entity.raw_properties)
216
239
  end
217
240
 
218
- alias_method :own_entries, :entries
241
+ alias_method :own_payload, :payload
219
242
  ##
220
- # # The RO-Crate's "payload" of the crate - a map of all the files/directories contained in the RO-Crate, where the
221
- # key is the destination path within the crate and the value is an Entry where the source data can be read.
243
+ # The file payload of the RO-Crate - a map of all the files/directories contained in the RO-Crate, where the
244
+ # key is the path relative to the crate's root, and the value is an Entry where the source data can be read.
222
245
  #
223
246
  # @return [Hash{String => Entry}>]
224
- def entries
247
+ def payload
225
248
  # Gather a map of entries, starting from the crate itself, then any directory data entities, then finally any
226
249
  # file data entities. This ensures in the case of a conflict, the more "specific" data entities take priority.
227
- entries = own_entries
250
+ entries = own_payload
228
251
  non_self_entities = default_entities.reject { |e| e == self }
229
252
  sorted_entities = (non_self_entities | data_entities).sort_by { |e| e.is_a?(ROCrate::Directory) ? 0 : 1 }
230
253
 
231
254
  sorted_entities.each do |entity|
232
- entity.entries.each do |path, entry|
255
+ entity.payload.each do |path, entry|
233
256
  entries[path] = entry
234
257
  end
235
258
  end
@@ -241,6 +264,29 @@ module ROCrate
241
264
  binding
242
265
  end
243
266
 
267
+ ##
268
+ # Remove the entity from the RO-Crate.
269
+ #
270
+ # @param entity [Entity, String] The entity or ID of an entity to remove from the crate.
271
+ # @param remove_orphaned [Boolean] Should linked contextual entities also be removed from the crate they are left
272
+ # dangling (nothing else is linked to them)?
273
+ #
274
+ # @return [Entity, nil] The entity that was deleted, or nil if nothing was deleted.
275
+ def delete(entity, remove_orphaned: true)
276
+ entity = dereference(entity) if entity.is_a?(String)
277
+ return unless entity
278
+
279
+ deleted = data_entities.delete(entity) || contextual_entities.delete(entity)
280
+
281
+ if deleted && remove_orphaned
282
+ crate_entities = crate.linked_entities(deep: true)
283
+ to_remove = (entity.linked_entities(deep: true) - crate_entities)
284
+ to_remove.each(&:delete)
285
+ end
286
+
287
+ deleted
288
+ end
289
+
244
290
  private
245
291
 
246
292
  def full_entry_path(relative_path)
@@ -3,7 +3,9 @@ module ROCrate
3
3
  # A class to represent a "Data Entity" within an RO-Crate.
4
4
  # Data Entities are the actual physical files and directories within the Crate.
5
5
  class DataEntity < Entity
6
- def self.format_id(id)
6
+ properties(%w[name contentSize dateModified encodingFormat identifier sameAs author])
7
+
8
+ def self.format_local_id(id)
7
9
  super.chomp('/')
8
10
  end
9
11
 
@@ -13,8 +15,6 @@ module ROCrate
13
15
  # @return [Class]
14
16
  def self.specialize(props)
15
17
  type = props['@type']
16
- id = props['@id']
17
- abs = URI(id)&.absolute? rescue false
18
18
  type = [type] unless type.is_a?(Array)
19
19
  if type.include?('Dataset')
20
20
  ROCrate::Directory
@@ -24,12 +24,13 @@ module ROCrate
24
24
  end
25
25
 
26
26
  ##
27
- # A map of all the files/directories associated with this DataEntity.
27
+ # The payload of all the files/directories associated with this DataEntity, mapped by their relative file path.
28
28
  #
29
29
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
30
- def entries
30
+ def payload
31
31
  {}
32
32
  end
33
+ alias_method :entries, :payload
33
34
 
34
35
  ##
35
36
  # A disk-safe filepath based on the ID of this DataEntity.
@@ -2,9 +2,7 @@ module ROCrate
2
2
  ##
3
3
  # A data entity that represents a directory of potentially many files and subdirectories (or none).
4
4
  class Directory < DataEntity
5
- properties(%w[name contentSize dateModified encodingFormat identifier sameAs])
6
-
7
- def self.format_id(id)
5
+ def self.format_local_id(id)
8
6
  super + '/'
9
7
  end
10
8
 
@@ -30,11 +28,11 @@ module ROCrate
30
28
  end
31
29
 
32
30
  ##
33
- # The "payload" of this directory - a map of all the files/directories, where the key is the destination path
31
+ # The payload of this directory - a map of all the files/directories, where the key is the destination path
34
32
  # within the crate and the value is an Entry where the source data can be read.
35
33
  #
36
34
  # @return [Hash{String => Entry}>]
37
- def entries
35
+ def payload
38
36
  entries = {}
39
37
  entries[filepath.chomp('/')] = @entry if @entry
40
38
 
@@ -29,12 +29,34 @@ module ROCrate
29
29
  end
30
30
 
31
31
  ##
32
- # Format the given ID with rules appropriate for this type.
32
+ # Format the given ID with rules appropriate for this type if it is local/relative, leave as-is if absolute.
33
+ #
34
+ # @param id [String] The candidate ID to be formatted.
35
+ # @return [String] The formatted ID.
36
+ def self.format_id(id)
37
+ begin
38
+ uri = URI(id)
39
+ rescue ArgumentError, URI::InvalidURIError
40
+ uri = nil
41
+ end
42
+
43
+ if uri&.absolute?
44
+ id
45
+ else
46
+ format_local_id(id)
47
+ end
48
+ end
49
+
50
+ ##
51
+ # Format the given local ID with rules appropriate for this type.
33
52
  # For example:
34
53
  # * contextual entities MUST be absolute URIs, or begin with: #
35
54
  # * files MUST NOT begin with ./
36
55
  # * directories MUST NOT begin with ./ (except for the crate itself), and MUST end with /
37
- def self.format_id(id)
56
+ #
57
+ # @param id [String] The candidate local ID to be formatted.
58
+ # @return [String] The formatted local ID.
59
+ def self.format_local_id(id)
38
60
  Addressable::URI.escape(id.sub(/\A\.\//, '')) # Remove initial ./ if present
39
61
  end
40
62
 
@@ -107,16 +129,26 @@ module ROCrate
107
129
  # @param id [String] The ID to query.
108
130
  # @return [Entity, nil]
109
131
  def dereference(id)
110
- crate.entities.detect { |e| e.canonical_id == crate.resolve_id(id) } if id
132
+ crate.dereference(id)
111
133
  end
112
-
113
134
  alias_method :get, :dereference
114
135
 
136
+ ##
137
+ # Remove this entity from the RO-Crate.
138
+ #
139
+ # @param remove_orphaned [Boolean] Should linked contextual entities also be removed from the crate (if nothing else is linked to them)?
140
+ #
141
+ # @return [Entity, nil] This entity, or nil if nothing was deleted.
142
+ def delete(remove_orphaned: true)
143
+ crate.delete(self, remove_orphaned: remove_orphaned)
144
+ end
145
+
115
146
  def id
116
147
  @properties['@id']
117
148
  end
118
149
 
119
150
  def id=(id)
151
+ @canonical_id = nil
120
152
  @properties['@id'] = self.class.format_id(id)
121
153
  end
122
154
 
@@ -168,13 +200,13 @@ module ROCrate
168
200
  #
169
201
  # @return [Addressable::URI]
170
202
  def canonical_id
171
- crate.resolve_id(id)
203
+ @canonical_id ||= crate.resolve_id(id)
172
204
  end
173
205
 
174
206
  ##
175
207
  # Is this entity local to the crate or an external reference?
176
208
  #
177
- # @return [boolean]
209
+ # @return [Boolean]
178
210
  def external?
179
211
  crate.canonical_id.host != canonical_id.host
180
212
  end
@@ -204,6 +236,33 @@ module ROCrate
204
236
  @properties.has_type?(type)
205
237
  end
206
238
 
239
+ ##
240
+ # Gather a list of entities linked to this one through its properties.
241
+ # @param deep [Boolean] If false, only consider direct links, otherwise consider transitive links.
242
+ # @param linked [Hash{String => Entity}] Discovered entities, mapped by their ID, to avoid loops when recursing.
243
+ # @return [Array<Entity>]
244
+ def linked_entities(deep: false, linked: {})
245
+ properties.each do |key, value|
246
+ value = [value] if value.is_a?(JSONLDHash)
247
+
248
+ if value.is_a?(Array)
249
+ value.each do |v|
250
+ if v.is_a?(JSONLDHash) && !linked.key?(v['@id'])
251
+ entity = v.dereference
252
+ linked[entity.id] = entity if entity
253
+ if deep
254
+ entity.linked_entities(deep: true, linked: linked).each do |e|
255
+ linked[e.id] = e
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ linked.values.compact
264
+ end
265
+
207
266
  private
208
267
 
209
268
  def default_properties
@@ -14,10 +14,10 @@ module ROCrate
14
14
  end
15
15
 
16
16
  ##
17
- # Write the source to the destination via a buffer.
17
+ # Write the entry's source to the destination via a buffer.
18
18
  #
19
19
  # @param dest [#write] An IO-like destination to write to.
20
- def write(dest)
20
+ def write_to(dest)
21
21
  input = source
22
22
  input = input.open('rb') if input.is_a?(Pathname)
23
23
  while (buff = input.read(4096))
@@ -2,14 +2,12 @@ module ROCrate
2
2
  ##
3
3
  # A data entity that represents a single file.
4
4
  class File < DataEntity
5
- properties(%w[name contentSize dateModified encodingFormat identifier sameAs])
6
-
7
5
  ##
8
6
  # Create a new ROCrate::File. PLEASE NOTE, the new file will not be added to the crate. To do this, call
9
7
  # Crate#add_data_entity, or just use Crate#add_file.
10
8
  #
11
9
  # @param crate [Crate] The RO-Crate that owns this file.
12
- # @param source [String, Pathname, ::File, #read, URI, nil] The source on the disk (or on the internet if a URI) where this file will be read.
10
+ # @param source [String, Pathname, ::File, URI, nil, #read] The source on the disk (or on the internet if a URI) where this file will be read.
13
11
  # @param crate_path [String] The relative path within the RO-Crate where this file will be written.
14
12
  # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this file.
15
13
  def initialize(crate, source, crate_path = nil, properties = {})
@@ -58,7 +56,7 @@ module ROCrate
58
56
  # (for compatibility with Directory#entries)
59
57
  #
60
58
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
61
- def entries
59
+ def payload
62
60
  remote? ? {} : { filepath => source }
63
61
  end
64
62
 
@@ -17,7 +17,15 @@ module ROCrate
17
17
  # @return [String] The rendered JSON-LD as a "prettified" string.
18
18
  def generate
19
19
  graph = crate.entities.map(&:properties).reject(&:empty?)
20
- JSON.pretty_generate('@context' => CONTEXT, '@graph' => graph)
20
+ JSON.pretty_generate('@context' => context, '@graph' => graph)
21
+ end
22
+
23
+ def context
24
+ @context || CONTEXT
25
+ end
26
+
27
+ def context= c
28
+ @context = c
21
29
  end
22
30
 
23
31
  private
@@ -2,7 +2,7 @@ module ROCrate
2
2
  ##
3
3
  # A contextual entity that represents an organization.
4
4
  class Organization < ContextualEntity
5
- properties(['name'])
5
+ properties(%w[name])
6
6
 
7
7
  private
8
8
 
@@ -12,26 +12,14 @@ module ROCrate
12
12
  # @return [String]
13
13
  attr_accessor :template
14
14
 
15
- def initialize(crate, properties = {})
15
+ def initialize(crate, source = nil, properties = {})
16
+ source ||= PreviewGenerator.new(self)
16
17
  @template = nil
17
- super(crate, nil, IDENTIFIER, properties)
18
- end
19
-
20
- ##
21
- # Generate the crate's `ro-crate-preview.html`.
22
- # @return [String] The rendered HTML as a string.
23
- def generate
24
- b = crate.get_binding
25
- renderer = ERB.new(template || ::File.read(DEFAULT_TEMPLATE))
26
- renderer.result(b)
18
+ super(crate, source, IDENTIFIER, properties)
27
19
  end
28
20
 
29
21
  private
30
22
 
31
- def source
32
- Entry.new(StringIO.new(generate))
33
- end
34
-
35
23
  def default_properties
36
24
  {
37
25
  '@id' => IDENTIFIER,
@@ -0,0 +1,40 @@
1
+ require 'erb'
2
+
3
+ module ROCrate
4
+ ##
5
+ # A class to handle generation of an RO-Crate's preview HTML in an IO-like way (to fit into an Entry).
6
+ class PreviewGenerator
7
+ ##
8
+ # @param preview [Preview] The RO-Crate preview object.
9
+ def initialize(preview)
10
+ @preview = preview
11
+ end
12
+
13
+ def read(*args)
14
+ io.read(*args)
15
+ end
16
+
17
+ ##
18
+ # Generate the crate's `ro-crate-preview.html`.
19
+ # @return [String] The rendered HTML as a string.
20
+ def generate
21
+ b = crate.get_binding
22
+ renderer = ERB.new(template)
23
+ renderer.result(b)
24
+ end
25
+
26
+ def template
27
+ @preview.template || ::File.read(Preview::DEFAULT_TEMPLATE)
28
+ end
29
+
30
+ def crate
31
+ @preview.crate
32
+ end
33
+
34
+ private
35
+
36
+ def io
37
+ @io ||= StringIO.new(generate)
38
+ end
39
+ end
40
+ end
@@ -2,7 +2,7 @@ module ROCrate
2
2
  ##
3
3
  # A class to represent a reference within an RO-Crate, to a remote file held on the internet somewhere.
4
4
  # It handles the actual reading/writing of bytes.
5
- class RemoteEntry
5
+ class RemoteEntry < Entry
6
6
  attr_reader :uri
7
7
 
8
8
  ##
@@ -13,17 +13,6 @@ module ROCrate
13
13
  @uri = uri
14
14
  end
15
15
 
16
- def write(dest)
17
- raise 'Cannot write to a remote entry!'
18
- end
19
-
20
- ##
21
- # Read from the source.
22
- #
23
- def read
24
- source.read
25
- end
26
-
27
16
  ##
28
17
  # @return [IO] An IO object for the remote resource.
29
18
  #