ro-crate 0.4.10 → 0.4.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) 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/crate.rb +48 -6
  5. data/lib/ro_crate/model/data_entity.rb +21 -8
  6. data/lib/ro_crate/model/directory.rb +5 -7
  7. data/lib/ro_crate/model/entity.rb +41 -4
  8. data/lib/ro_crate/model/entry.rb +2 -2
  9. data/lib/ro_crate/model/file.rb +6 -4
  10. data/lib/ro_crate/model/metadata.rb +9 -1
  11. data/lib/ro_crate/model/organization.rb +1 -1
  12. data/lib/ro_crate/model/preview.rb +3 -15
  13. data/lib/ro_crate/model/preview_generator.rb +40 -0
  14. data/lib/ro_crate/model/remote_entry.rb +1 -12
  15. data/lib/ro_crate/reader.rb +61 -23
  16. data/lib/ro_crate/writer.rb +4 -4
  17. data/lib/ro_crate.rb +2 -1
  18. data/ro_crate.gemspec +3 -3
  19. data/test/crate_test.rb +58 -3
  20. data/test/directory_test.rb +21 -21
  21. data/test/entity_test.rb +117 -3
  22. data/test/fixtures/biobb_hpc_workflows-condapack.zip +0 -0
  23. data/test/fixtures/conflicting_data_directory/info.txt +1 -0
  24. data/test/fixtures/conflicting_data_directory/nested.txt +1 -0
  25. data/test/fixtures/misc_data_entity_crate/ro-crate-metadata.json +33 -0
  26. data/test/fixtures/ro-crate-galaxy-sortchangecase/LICENSE +176 -0
  27. data/test/fixtures/ro-crate-galaxy-sortchangecase/README.md +6 -0
  28. data/test/fixtures/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json +140 -0
  29. data/test/fixtures/ro-crate-galaxy-sortchangecase/sort-and-change-case.ga +118 -0
  30. data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/input.bed +3 -0
  31. data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/output_exp.bed +3 -0
  32. data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/sort-and-change-case-test.yml +8 -0
  33. data/test/fixtures/sparse_directory_crate/ro-crate-preview.html +60 -59
  34. data/test/fixtures/workflow-0.2.0/ro-crate-metadata.jsonld +5 -5
  35. data/test/fixtures/workflow-0.2.0.zip +0 -0
  36. data/test/reader_test.rb +110 -58
  37. data/test/test_helper.rb +5 -1
  38. data/test/writer_test.rb +70 -2
  39. metadata +26 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb6b4eba94b7190b7888cf3b1ddc8a8c6d0b464834ff48c45d9b50e6b13844ce
4
- data.tar.gz: 70e0079caadfa3f17745e918f8109ec5908a8aa0c87d80aeaebfc55fdc03a9a8
3
+ metadata.gz: 5d94d65b4375c3923a828f763c749e9958d975c2f3fd3ee55b996f718cd9317c
4
+ data.tar.gz: 3ce5e362db42e5a790991bef4fe252d507a35e83eff00a58b1fe99ce4f5808e6
5
5
  SHA512:
6
- metadata.gz: 62c9875be216987ceaca7e47f92112ba744109e4df9f8dcbba9023211ef3fc278f74f8ce7421523a0f443000f251627735eb01ce2357e9e526e36c23818bbba0
7
- data.tar.gz: 0b5be2eedc7a045ccf25db4e3d800107f30d00b62e1e4b5bdb1deed1eed941897f46621bef9fa9a012891bc058857de82d6afebeb122153f8ffc944b8ca06a7a
6
+ metadata.gz: dd26c3d0fc2783f7424ba022ae03d5cc14c0e4aa7f96e2c6b682f82be3af9cee5cacb1480b414b5ab2f37d08e19b38b377455afd1ec74017901788a4a6500bab
7
+ data.tar.gz: eb6f983303b2b50fafaec3a03c4c3f855b4c12a4fcd80611d20916ec1f64c078973e842d1425c3101a1609b16db3bdfbe56eccde2a703de0efc360df0a48550f
data/Gemfile.lock CHANGED
@@ -1,31 +1,31 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ro-crate (0.4.10)
5
- addressable (~> 2.7.0)
4
+ ro-crate (0.4.14)
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/).
@@ -25,6 +25,15 @@ module ROCrate
25
25
  super(self, nil, id, properties)
26
26
  end
27
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
+
28
37
  ##
29
38
  # Create a new file and add it to the crate.
30
39
  #
@@ -168,6 +177,15 @@ module ROCrate
168
177
  @preview ||= ROCrate::Preview.new(self)
169
178
  end
170
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
+
171
189
  ##
172
190
  # All the entities within the crate. Includes contextual entities, data entities, the crate itself and its metadata file.
173
191
  #
@@ -220,32 +238,56 @@ module ROCrate
220
238
  entity.class.new(crate, entity.id, entity.raw_properties)
221
239
  end
222
240
 
223
- alias_method :own_entries, :entries
241
+ alias_method :own_payload, :payload
224
242
  ##
225
- # # The RO-Crate's "payload" of the crate - a map of all the files/directories contained in the RO-Crate, where the
226
- # 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.
227
245
  #
228
246
  # @return [Hash{String => Entry}>]
229
- def entries
247
+ def payload
230
248
  # Gather a map of entries, starting from the crate itself, then any directory data entities, then finally any
231
249
  # file data entities. This ensures in the case of a conflict, the more "specific" data entities take priority.
232
- entries = own_entries
250
+ entries = own_payload
233
251
  non_self_entities = default_entities.reject { |e| e == self }
234
252
  sorted_entities = (non_self_entities | data_entities).sort_by { |e| e.is_a?(ROCrate::Directory) ? 0 : 1 }
235
253
 
236
254
  sorted_entities.each do |entity|
237
- entity.entries.each do |path, entry|
255
+ entity.payload.each do |path, entry|
238
256
  entries[path] = entry
239
257
  end
240
258
  end
241
259
 
242
260
  entries
243
261
  end
262
+ alias_method :entries, :payload
244
263
 
245
264
  def get_binding
246
265
  binding
247
266
  end
248
267
 
268
+ ##
269
+ # Remove the entity from the RO-Crate.
270
+ #
271
+ # @param entity [Entity, String] The entity or ID of an entity to remove from the crate.
272
+ # @param remove_orphaned [Boolean] Should linked contextual entities also be removed from the crate they are left
273
+ # dangling (nothing else is linked to them)?
274
+ #
275
+ # @return [Entity, nil] The entity that was deleted, or nil if nothing was deleted.
276
+ def delete(entity, remove_orphaned: true)
277
+ entity = dereference(entity) if entity.is_a?(String)
278
+ return unless entity
279
+
280
+ deleted = data_entities.delete(entity) || contextual_entities.delete(entity)
281
+
282
+ if deleted && remove_orphaned
283
+ crate_entities = crate.linked_entities(deep: true)
284
+ to_remove = (entity.linked_entities(deep: true) - crate_entities)
285
+ to_remove.each(&:delete)
286
+ end
287
+
288
+ deleted
289
+ end
290
+
249
291
  private
250
292
 
251
293
  def full_entry_path(relative_path)
@@ -3,9 +3,7 @@ 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_local_id(id)
7
- super.chomp('/')
8
- end
6
+ properties(%w[name contentSize dateModified encodingFormat identifier sameAs author])
9
7
 
10
8
  ##
11
9
  # Return an appropriate specialization of DataEntity for the given properties.
@@ -13,23 +11,38 @@ module ROCrate
13
11
  # @return [Class]
14
12
  def self.specialize(props)
15
13
  type = props['@type']
16
- id = props['@id']
17
- abs = URI(id)&.absolute? rescue false
18
14
  type = [type] unless type.is_a?(Array)
19
15
  if type.include?('Dataset')
20
16
  ROCrate::Directory
21
- else
17
+ elsif type.include?('File')
22
18
  ROCrate::File
19
+ else
20
+ self
23
21
  end
24
22
  end
25
23
 
26
24
  ##
27
- # A map of all the files/directories associated with this DataEntity.
25
+ # Create a new ROCrate::DataEntity. This entity represents something that is neither a file or directory, but
26
+ # still constitutes part of the crate.
27
+ # PLEASE NOTE, the new data entity will not be added to the crate. To do this, call Crate#add_data_entity.
28
+ #
29
+ # @param crate [Crate] The RO-Crate that owns this directory.
30
+ # @param source [nil] Ignored. For compatibility with the File and Directory constructor signatures.
31
+ # @param id [String, nil] An ID to identify this DataEntity, or nil to auto-generate an appropriate one,
32
+ # (or determine via the properties param)
33
+ # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this DataEntity.
34
+ def initialize(crate, source = nil, id = nil, properties = {})
35
+ super(crate, id, properties)
36
+ end
37
+
38
+ ##
39
+ # The payload of all the files/directories associated with this DataEntity, mapped by their relative file path.
28
40
  #
29
41
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
30
- def entries
42
+ def payload
31
43
  {}
32
44
  end
45
+ alias_method :entries, :payload
33
46
 
34
47
  ##
35
48
  # A disk-safe filepath based on the ID of this DataEntity.
@@ -2,14 +2,12 @@ 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
5
  def self.format_local_id(id)
8
- super + '/'
6
+ super.chomp('/') + '/'
9
7
  end
10
8
 
11
9
  ##
12
- # Create a new Directory. PLEASE NOTE, the new directory will not be added to the crate. To do this, call
10
+ # Create a new ROCrate::Directory. PLEASE NOTE, the new directory will not be added to the crate. To do this, call
13
11
  # Crate#add_data_entity, or just use Crate#add_directory.
14
12
  #
15
13
  # @param crate [Crate] The RO-Crate that owns this directory.
@@ -26,15 +24,15 @@ module ROCrate
26
24
  crate_path = source_directory.basename.to_s if crate_path.nil?
27
25
  end
28
26
 
29
- super(crate, crate_path, properties)
27
+ super(crate, nil, crate_path, properties)
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
 
@@ -129,16 +129,26 @@ module ROCrate
129
129
  # @param id [String] The ID to query.
130
130
  # @return [Entity, nil]
131
131
  def dereference(id)
132
- crate.entities.detect { |e| e.canonical_id == crate.resolve_id(id) } if id
132
+ crate.dereference(id)
133
133
  end
134
-
135
134
  alias_method :get, :dereference
136
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
+
137
146
  def id
138
147
  @properties['@id']
139
148
  end
140
149
 
141
150
  def id=(id)
151
+ @canonical_id = nil
142
152
  @properties['@id'] = self.class.format_id(id)
143
153
  end
144
154
 
@@ -190,13 +200,13 @@ module ROCrate
190
200
  #
191
201
  # @return [Addressable::URI]
192
202
  def canonical_id
193
- crate.resolve_id(id)
203
+ @canonical_id ||= crate.resolve_id(id)
194
204
  end
195
205
 
196
206
  ##
197
207
  # Is this entity local to the crate or an external reference?
198
208
  #
199
- # @return [boolean]
209
+ # @return [Boolean]
200
210
  def external?
201
211
  crate.canonical_id.host != canonical_id.host
202
212
  end
@@ -226,6 +236,33 @@ module ROCrate
226
236
  @properties.has_type?(type)
227
237
  end
228
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
+
229
266
  private
230
267
 
231
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,16 @@ 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])
5
+ def self.format_local_id(id)
6
+ super.chomp('/')
7
+ end
6
8
 
7
9
  ##
8
10
  # Create a new ROCrate::File. PLEASE NOTE, the new file will not be added to the crate. To do this, call
9
11
  # Crate#add_data_entity, or just use Crate#add_file.
10
12
  #
11
13
  # @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.
14
+ # @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
15
  # @param crate_path [String] The relative path within the RO-Crate where this file will be written.
14
16
  # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this file.
15
17
  def initialize(crate, source, crate_path = nil, properties = {})
@@ -34,7 +36,7 @@ module ROCrate
34
36
  crate_path = source.to_s if source.is_a?(URI) && source.absolute?
35
37
  end
36
38
 
37
- super(crate, crate_path, properties)
39
+ super(crate, nil, crate_path, properties)
38
40
 
39
41
  if source.is_a?(URI) && source.absolute?
40
42
  @entry = RemoteEntry.new(source)
@@ -58,7 +60,7 @@ module ROCrate
58
60
  # (for compatibility with Directory#entries)
59
61
  #
60
62
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
61
- def entries
63
+ def payload
62
64
  remote? ? {} : { filepath => source }
63
65
  end
64
66
 
@@ -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
  #